본문 바로가기

항해99

23.10.19 항해 99 16기 실전 프로젝트 14일차

오늘 공부한 것

* WebSocket 사용한 채팅 기능 구현 (Stomp 도입)

 

Stomp 란?메시징 전송을 효율적으로 하기 위해 나온 프로토콜이고 기본적으로 pub / sub 구조로 되어있어 메세지를 발송하고, 메세지를 받아 처리하는 부분이 확실히 정해져 있기 때문에 개발하는 입장에서 명확하게 인지하고 개발할 수 있는 이점이 있다. 또한 통신 메세지의 헤더에 값을 세팅할 수 있어 헤다 값을 기반으로 통신시 인증처리를 구현하는 것도 가능

pub / sub 란 메세지를 공급하는 주체와 소비하는 주체를 분리하여 제공하는 메세징 방법이다.

예를 들어 우체통 (topic) 이 있으면 집배원 (publisher) 이 신문을 우체통에 배달하는 액션이 있고,

기다렸다가 빼서 보는 구독자 (subscriber) 의 액션이 있다 여기서 구독자는 여러명이 될 수 있다

 

* 채팅방 생성 : pub / sub 구현을 위한 Topic 생성

* 채팅방 입장 : Topic 을 구독

* 메세지 주고 받음 : 해당 Topic 으로 메세지를 발송하거나 (pub) 메세지를 받는다 (sub)

 

코드는 아래와 같이 변경 되었다

 

1. Config

@Configuration
@EnableWebSocketMessageBroker // 웹소켓 서버를 사용한다는 설정
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // 서버 -> 클라이언트로 발행하는 메세지에 대한 endpoint 설정 : 구독
        config.enableSimpleBroker("/sub"); // 발행자가 queue (1:1) topic (1:다)의 경로로 메세지를 주면 구독자들에게 전달
        // 클라이언트 -> 서버로 발행하는 메세지에 대한 endpoint 설정 : 구독에 대한 메세지
        config.setApplicationDestinationPrefixes("/pub"); // 메세지 앞에 app이 붙어있는 경로로 발신되면 해당 경로를 처리하고 있는 핸들러로 전달됨 @MessageMapping 어노테이션이 붙은 곳을 타겟으로 한다는 설정
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 웹 소켓이 hanshake를 하기 위해 연결하는 endpoint
        registry.addEndpoint("/ws-stomp") // WebSocket 또는 SockJS가 웹소켓 핸드세이크 커넥션을 생성할 경로
                .setAllowedOriginPatterns("*")
                .withSockJS(); // WebSocket을 지원하지 않는 브라우저에서 HTTP의 Polling과 같은 방식으로 WebSocket의 요청을 수행하도록 도와줌
    }
}

 

2. Controller

@RequiredArgsConstructor
@Controller
public class ChatController {

    private final SimpMessageSendingOperations messagingTemplate; //특정 Broker로 메세지를 전달

    @MessageMapping("/chat/message")
    public void message(ChatMessageDto message) {
        if (ChatMessageDto.MessageType.ENTER.equals(message.getType()))
            message.setMessage(message.getSender() + "님이 입장하셨습니다.");
        messagingTemplate.convertAndSend("/sub/chat/room/" + message.getRoomId(), message);
    }
}
@RequiredArgsConstructor
@Controller
@RequestMapping("/chat")
public class ChatRoomController {

    private final ChatRoomRepository chatRoomRepository;

    // 채팅 리스트 화면
    @GetMapping("/room")
    public String room(Model model) {
        return "/chat/room";
    }

    // 모든 채팅방 목록 반환
    @GetMapping("/rooms")
    @ResponseBody
    public List<ChatRoomDto> room () {
        return chatRoomRepository.findAllRoom();
    }

    // 채팅방 생성
    @PostMapping("/room")
    @ResponseBody
    public ChatRoomDto createRoom(@RequestParam String name) {
        return chatRoomRepository.createChatRoom(name);
    }

    // 채팅방 입장화면
    @GetMapping("/room/enter/{roomId}")
    public String roomDetail(Model model, @PathVariable String roomId) {
        model.addAttribute("roomId", roomId);
        return "/chat/roomdetail";
    }

    // 특정 채팅방 조회
    @GetMapping("room/{roomId}")
    @ResponseBody
    public ChatRoomDto roomInfo(@PathVariable String roomId) {
        return chatRoomRepository.findRoomById(roomId);
    }
}

 

3. Dto

@Getter
@Setter
public class ChatMessageDto {
    // 메세지 타입 : 입장, 채팅
    public enum MessageType{
        ENTER,

        TALK
    }

    private MessageType type; // 메세지 타입
    private String roomId; // 방번호
    private String sender; //메세지 보낸사람
    private String message; // 메세지
}
@Getter
@Setter
public class ChatRoomDto {
    private String roomId;
    private String name;

    public static ChatRoomDto create(String name) {
        ChatRoomDto chatRoomDto = new ChatRoomDto();
        chatRoomDto.roomId = UUID.randomUUID().toString(); // UUID : 네트워크 상에서 고유성이 보장되는 id
        chatRoomDto.name = name;
        return chatRoomDto;
    }
}

 

4. Repository

@Repository
public class ChatRoomRepository {

    private Map<String, ChatRoomDto> chatRoomDtoMap;

    // 의존하는 객체를 설정한 이후 초기화 작업을 수행하는 메서드에 적용
    @PostConstruct
    private void init() {
        chatRoomDtoMap = new LinkedHashMap<>();
    }

    public List<ChatRoomDto> findAllRoom(){
        // 채팅방 생성순서 최근 순으로 반환
        List chatRooms = new ArrayList<>(chatRoomDtoMap.values());
        Collections.reverse(chatRooms);
        return chatRooms;
    }

    public ChatRoomDto findRoomById(String id) {
        return chatRoomDtoMap.get(id);
    }

    public ChatRoomDto createChatRoom(String name) {
        ChatRoomDto chatRoomDto = ChatRoomDto.create(name);
        chatRoomDtoMap.put(chatRoomDto.getRoomId(), chatRoomDto);
        return chatRoomDto;
    }
}

 

5. 결과