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
'Web > Spring' 카테고리의 다른 글
[SPRING] WebSocket (0) | 2022.06.21 |
---|