본문 바로가기

Web/Spring

[SPRING] STOMP

STOMP

WebSocket을 사용하기 위해서는 WebSocketHandler가 필요하며, 어떠한 handler를 상속받는지에 따라 어떤 타입의 메세지를 처리할지 결정할 수 있습니다. 하지만 메세지가 어떤 포맷으로 전달될지, 어떤 내용을 담고 있는지에 대해서는 정의하지 않고 있기 때문에 이 부분을 직접 구현해야 한다는 번거로움이 있습니다.

이를 해결하기 위해 서브 프로토콜로 메세지의 형식, 유형, 내용 등을 정의해주는 프로토콜인 STOMP를 사용합니다.

 


 

STOMP의 형식

COMMAND
header1:value1
header2:value2

Body^@

STOMP의 형식은 위와 같습니다.

COMMAND는 어떠한 종류의 메시지인지 알려주는 용도로, 대표적으로 SEND, SUBSCRIBE 등이 있습니다.

또한 header와 value를 통해 메세지의 수신 대상과 메세지에 대한 정보 등을 표시할 수 있습니다. header의 대표적인 예로는 destination이 있는데, 어디로 메세지를 보낼지 또는 어떤 채팅방을 구독할지를 지정할 수 있습니다.

 

SUBSCRIBE
destination: /topic/chat/room/5

^@

위의 메세지는 5번 채팅방을 구독하는 것을 표현한 예시입니다.

 

SEND
destination: /pub/chat
content-type: application/json

{"chatRoomId":5, "type":"MESSAGE", "writer":"clientB"}^@

위의 메세지는 클라이언트 B가 5번 채팅방에 메세지를 보내는 것을 표현한 예시입니다.

 


 

STOMP 코드 예제

- WebSocketConfig.java

@Configuration
@EnableWebSocketMessageBroker
@RequiredArgsConstructor
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/sub"); //클라이언트로 메세지 보낼 때의 prefix
        registry.setApplicationDestinationPrefixes("/pub"); //클라이언트가 메세지 보낼 때의 prefix
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/stomp/chat").setAllowedOrigins("*").withSockJS(); //handshake가 될 endpoint
    }
}

 

- ChatRoomDTO.java

@Getter
@Setter
public class ChatRoomDTO {

    private String roomId;
    private String name;
    private Set<WebSocketSession> sessions = new HashSet<>();

    public static ChatRoomDTO create(String name){
        ChatRoomDTO room = new ChatRoomDTO();

        room.roomId = UUID.randomUUID().toString();
        room.name = name;
        return room;
    }
}

- ChatMessageDTO.java

@Getter
@Setter
public class ChatMessageDTO {

    private String roomId;
    private String writer;
    private String message;
}

 

- ChatRoomRepository.java

@Repository
public class ChatRoomRepository {

    private Map<String, ChatRoomDTO> chatRoomDTOMap;

    @PostConstruct
    private void init(){
        chatRoomDTOMap = new LinkedHashMap<>();
    }

    public List<ChatRoomDTO> findAllRooms(){
        List<ChatRoomDTO> result = new ArrayList<>(chatRoomDTOMap.values());
        Collections.reverse(result);

        return result;
    }

    public ChatRoomDTO findRoomById(String id){
        return chatRoomDTOMap.get(id);
    }

    public ChatRoomDTO createChatRoomDTO(String name){
        ChatRoomDTO room = ChatRoomDTO.create(name);
        chatRoomDTOMap.put(room.getRoomId(), room);

        return room;
    }
}

 

- RoomController.java

@Controller
@RequiredArgsConstructor
@RequestMapping(value = "/chat")
public class RoomController {

    private final ChatRoomRepository repository;

    //채팅방 목록 조회
    @GetMapping(value = "/rooms")
    public ModelAndView rooms(){
        ModelAndView mv = new ModelAndView("chat/rooms");
        mv.addObject("list", repository.findAllRooms());

        return mv;
    }

    //채팅방 개설
    @PostMapping(value = "/room")
    public String create(@RequestParam String name, RedirectAttributes rttr){
        rttr.addFlashAttribute("roomName", repository.createChatRoomDTO(name));
        return "redirect:/chat/rooms";
    }

    //채팅방 조회
    @GetMapping(value = "/room")
    public ModelAndView getRoom(@RequestParam(value = "roomId") String roomId){
        ModelAndView mv = new ModelAndView("chat/room");
        mv.addObject("room", repository.findRoomById(roomId));
        
        return mv;
    }
}

- StompChatController.java

@Controller
@RequiredArgsConstructor
public class StompChatController {

    private final SimpMessagingTemplate template;

    @MessageMapping(value = "/chat/enter")
    public void enter(ChatMessageDTO message){
        message.setMessage(message.getWriter() + "님이 채팅방에 참여하였습니다.");
        template.convertAndSend("/sub/chat/room/" + message.getRoomId(), message);
    }

    @MessageMapping(value = "/chat/message")
    public void message(ChatMessageDTO message){
        template.convertAndSend("/sub/chat/room/" + message.getRoomId(), message);
    }
}

 

References

[Spring Boot] WebSocket과 채팅 (3) - STOMP (tistory.com)

'Web > Spring' 카테고리의 다른 글

[SPRING] WebSocket  (0) 2022.06.21