[SpringBoot] Websocket을 이용한 Chatting Server 구현



  • Category : Spring
  • Tag : Spring


프로젝트 생성

Java버전 및 Gradle 선택

img

Spring 버전, 의존성 추가

img

패키지 구조

chat
    WebSockConfig
    WebSocketHandler
    - controller
        - ChatController
    - dto
        - ChatMessage
        - ChatRoom
    - service
        - ChatService

WebSocket 기본설정

  1. WebSockConfig
     import org.springframework.context.annotation.Configuration;
     import org.springframework.web.socket.WebSocketHandler;
     import org.springframework.web.socket.config.annotation.EnableWebSocket;
     import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
     import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
    
     import lombok.RequiredArgsConstructor;
    
     @RequiredArgsConstructor
     @Configuration
     @EnableWebSocket
     public class WebSockConfig implements WebSocketConfigurer {
         private final WebSocketHandler webSocketHandler;
    
         @Override
         public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
             registry.addHandler(webSocketHandler, "ws").setAllowedOrigins("*");
         }
     }
    
  2. WebSocketHandler
     import org.springframework.stereotype.Component;
     import org.springframework.web.socket.TextMessage;
     import org.springframework.web.socket.WebSocketSession;
     import org.springframework.web.socket.handler.TextWebSocketHandler;
    
     import com.blackcode.www.chat.dto.ChatMessage;
     import com.blackcode.www.chat.dto.ChatRoom;
     import com.blackcode.www.chat.service.ChatService;
     import com.fasterxml.jackson.databind.ObjectMapper;
    
     import lombok.RequiredArgsConstructor;
     import lombok.extern.slf4j.Slf4j;
    
     @Slf4j
     @RequiredArgsConstructor
     @Component
     public class WebSocketHandler extends TextWebSocketHandler {
         private final ObjectMapper objectMapper;
         private final ChatService chatService;
            
         @Override
         protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
             String payload = message.getPayload();
             log.info("{}", payload);
             ChatMessage chatMessage = objectMapper.readValue(payload, ChatMessage.class);
                
             ChatRoom chatRoom = chatService.findRoomById(chatMessage.getRoomId());
             chatRoom.handlerActions(session, chatMessage, chatService);
         }
     }
    

Controller 생성

  • ChatController
      import java.util.List;
    
      import org.springframework.web.bind.annotation.GetMapping;
      import org.springframework.web.bind.annotation.PostMapping;
      import org.springframework.web.bind.annotation.RequestMapping;
      import org.springframework.web.bind.annotation.RequestParam;
      import org.springframework.web.bind.annotation.RestController;
    
      import com.blackcode.www.chat.dto.ChatRoom;
      import com.blackcode.www.chat.service.ChatService;
    
      import lombok.RequiredArgsConstructor;
    
      @RequiredArgsConstructor
      @RestController
      @RequestMapping("/chat")
      public class ChatController {
    
          private final ChatService chatService;
            
          @PostMapping
          public ChatRoom createRoom(@RequestParam String name) {
              return chatService.createRoom(name);
          }
            
          @GetMapping
          public List<ChatRoom> findAllRoom() {
              return chatService.findAllRoom();
          }
      }
    

    Service 생성

  • ChatService Class
      import java.io.IOException;
      import java.util.ArrayList;
      import java.util.LinkedHashMap;
      import java.util.List;
      import java.util.Map;
      import java.util.UUID;
    
      import org.springframework.stereotype.Service;
      import org.springframework.web.socket.TextMessage;
      import org.springframework.web.socket.WebSocketSession;
    
      import com.blackcode.www.chat.dto.ChatRoom;
      import com.fasterxml.jackson.databind.ObjectMapper;
    
      import jakarta.annotation.PostConstruct;
      import lombok.RequiredArgsConstructor;
      import lombok.extern.slf4j.Slf4j;
    
      @Slf4j
      @RequiredArgsConstructor
      @Service
      public class ChatService {
          private final ObjectMapper objectMapper;
          private Map<String, ChatRoom> chatRooms;
            
          @PostConstruct
          private void init() {
              chatRooms = new LinkedHashMap<>();
          }
            
          public List<ChatRoom> findAllRoom() {
              return new ArrayList<>(chatRooms.values());
          }
            
          public ChatRoom findRoomById(String roomId) {
              return chatRooms.get(roomId);
          }
            
          public ChatRoom createRoom(String name) {
              String randomId = UUID.randomUUID().toString();
              ChatRoom chatRoom = ChatRoom.builder()
                      .roomId(randomId)
                      .name(name)
                      .build();
              chatRooms.put(randomId, chatRoom);
              log.info(chatRoom.toString());
              return chatRoom;
          }
            
          public <T> void sendMessage(WebSocketSession session, T message) {
              try{
                  session.sendMessage(new TextMessage(objectMapper.writeValueAsString(message)));
              } catch (IOException e) {
                  log.error(e.getMessage(), e);
              }
          }
      }
    

dto 생성

  • ChatMessage Class
      import lombok.Getter;
      import lombok.Setter;
    
      @Getter
      @Setter
      public class ChatMessage {
          public enum MessageType{
              ENTER, TALK
          }
    
          private MessageType type;
          private String roomId;
          private String sender;
          private String message;
      }
    
  • ChatRoom Class
      import java.util.HashSet;
      import java.util.Set;
    
      import org.springframework.web.socket.WebSocketSession;
    
      import com.blackcode.www.chat.service.ChatService;
    
      import lombok.Builder;
      import lombok.Getter;
      import lombok.extern.slf4j.Slf4j;
    
      @Slf4j
      @Getter
      public class ChatRoom {
          private String roomId;
          private String name;
          private Set<WebSocketSession> sessions = new HashSet<>();
            
          @Builder
          public ChatRoom(String roomId, String name) {
              log.info("roomID : "+roomId);
              log.info("name : "+name);
              this.roomId = roomId;
              this.name = name;
          }
            
          public void handlerActions(WebSocketSession session, ChatMessage chatMessage, ChatService chatService) {
              if (chatMessage.getType().equals(ChatMessage.MessageType.ENTER)) {
                  sessions.add(session);
                  chatMessage.setMessage(chatMessage.getSender() + "님이 입장했습니다.");
              }
              sendMessage(chatMessage, chatService);
            
          }
            
          private <T> void sendMessage(T message, ChatService chatService) {
              sessions.parallelStream()
                      .forEach(session -> chatService.sendMessage(session, message));
          }
      }
    

1. 채팅방 개설

  • post
    • url : http://127.0.0.1:8080/chat
    • Body
      • key:name
      • Value:방제목

post 방식으로 key/value 값을 송출하고 나면 roomId를 return받는다.
roomId값 메모 필요

2. 방 생성 조회

  • get
    • url : http://127.0.0.1:8080/chat

3. 방 진입

img

  • WebSocket Text Client
  • url : ws://127.0.0.1:8080/ws
  • Request
      {
          "type":"ENTER",
          "roomId":"<기록해둔 roomId 입력>",
          "sender":"<참가자 ID>",
          "message":"<채팅할때만 message 입력>"
      }
    

4. 채팅 진행

img

  • WebSocket Text Client
  • url : ws://127.0.0.1:8080/ws
  • Request
      {
          "type":"TALK",
          "roomId":"<기록해둔 roomId 입력>",
          "sender":"<참가자 ID>",
          "message":"<채팅할때만 message 입력>"
      }
    

Share this post