채팅 메시지에 대한 정보를 담는 클래스 : 채팅 내용에 대한 DTO
채팅 내용은 크게 들어오는 사람에 대한 환영 메시지에 대한 ENTER과 방에 있는 사람들이 채팅을 칠 때 사용하는 TALK 두 가지로 메시지 타입을 나눕니다. 이때 타입은 ENUM으로 선언합니다.
다음으로 어떤 방에서 채팅이 오가는지 확인하기 위한 방번호,채팅을 보낸 사람,메시지,채팅발송 시간 등을 변수로 선언합니다.
package org.codej.websocket.domain;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ChatDto {
//메시지 타입 : 입장 채팅
public enum MessageType{
ENTER, TALK
}
private MessageType type; //메시지 타입
private String roomId;// 방 번호
private String sender;//채팅을 보낸 사람
private String message;// 메세지
private String time; // 채팅 발송 시간
}
package org.codej.websocket.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.codej.websocket.domain.ChatRoom;
import org.springframework.stereotype.Service;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.*;
@Slf4j
@Data
@Service
public class ChatService {
private final ObjectMapper mapper;
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 roomId = UUID.randomUUID().toString();
//Builder를 사용하여 ChatRoom 을 Build
ChatRoom room = ChatRoom.builder()
.roomId(roomId)
.name(name)
.build();
chatRooms.put(roomId,room);//랜덤 아이디와 room 정보를 Map 에 저장
return room;
}
public <T> void sendMessage(WebSocketSession session, T message){
try{
session.sendMessage(new TextMessage(mapper.writeValueAsString(message)));;
}catch (IOException e){
log.error(e.getMessage(),e);
}
}
}
handleAction
: Message Type에 따라서 session(클라이언트)에게 메시지를 전달하기 위한 메서드입니다.sendMessage
는 sessions 에 담긴 모든 session에 handleAction 으로 부터 넘어온 message 를 전달할 수 있도록 하는 메서드 입니다.package org.codej.websocket.domain;
import lombok.Builder;
import lombok.Data;
import org.codej.websocket.service.ChatService;
import org.springframework.web.socket.WebSocketSession;
import java.util.HashSet;
import java.util.Set;
@Data
public class ChatRoom {
private String roomId;//채팅방 아이디
private String name;//채팅방 이름
private Set<WebSocketSession> sessions = new HashSet<>();
@Builder
public ChatRoom(String roomId,String name){
this.roomId = roomId;
this.name = name;
}
public void handleAction(WebSocketSession session, ChatDto message, ChatService service){
//message 에 담긴 타입을 확인한다.
//이때 message 에서 getType 으로 가져온 내용이
//chatDto 의 열거형인 MessageType 안에 있는 ENTER 과 동일한 값이라면
if(message.getType().equals(ChatDto.MessageType.ENTER)){
//sessions 에 넘어온 session 을 담고,
sessions.add(session);
//message 에는 입장하였다는 메시지를 띄워줍니다.
message.setMessage(message.getSender() + " 님이 입장하였습니다.");
sendMessage(message,service);
} else if (message.getType().equals(ChatDto.MessageType.TALK)) {
message.setMessage(message.getMessage());
sendMessage(message,service);
}
}
public <T> void sendMessage(T message, ChatService service){
sessions.parallelStream().forEach(sessions -> service.sendMessage(sessions,message));
}
}
페이로드(paylaod)란?
페이로드란 전송되는 데이터를 의미합니다.
데이터를 전송 할 때, Header와 META데이터, 에러체크 비트 등과 같은 다양한 요소들을 함께 보내 데이터 전송 효율과 안정성을 높히게 됩니다.
이때, 보내고자 하는 데이터 자체를 의미하는 것이 페이로드입니다.
예를 들어 택배 배송을 보내고 받을 때 택배 물건이 페이로드고 송장이나 박스 등은 부가적인 것이기 때문에 페이로드가 아닙니다.
다음 JSON에서 페이로드는 'data'입니다. 나머지는 통신을 하는데 있어 용이하게 해주는 부가적 정보들입니다.
{
"status":
"from":"localhost",
"to":"http://codej.com/chatroom/1",
"method":"GET",
"data":{"message":"Welcome my room!"}
}
package org.codej.websocket.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.codej.websocket.domain.ChatDto;
import org.codej.websocket.domain.ChatRoom;
import org.codej.websocket.service.ChatService;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.util.ArrayList;
import java.util.List;
@Component
@Slf4j
@RequiredArgsConstructor
public class ChatHandler extends TextWebSocketHandler {
private final ObjectMapper mapper;
private final ChatService service;
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
String payload = (String) message.getPayload();
log.info("payload : {}",payload);
// TextMessage intialGretting = new TextMessage("Welcome to Chat Server");
//JSON -> Java Object
ChatDto chatMessage = mapper.readValue(payload, ChatDto.class);
log.info("session : {}",chatMessage.toString());
ChatRoom room = service.findRoomById(chatMessage.getRoomId());
log.info("room : {}",room.toString());
room.handleAction(session,chatMessage, service);
}
/** Client가 접속 시 호출되는 메서드*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
log.info(session + " 클라이언트 접속");
}
/** client가 접속 시 호출되는 메서드*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
log.info(session + " 클라이언트 접속 해제");
}
}
@EnableWebSocket
어노테이션을 사용하여 WebSocket을 활성화 하도록 합니다.package org.codej.websocket.config;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.codej.websocket.handler.ChatHandler;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@RequiredArgsConstructor
@EnableWebSocket
@Slf4j
public class WebSocketConfig implements WebSocketConfigurer {
// WebSocketHandler 에 관한 생성자 추가
private final ChatHandler chatHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// endpoint 설정 : /ws/chat
// 이를 통해서 ws://localhost:8080/ws/chat 으로 요청이 들어오면 websocket 통신을 진행합니다.
registry.addHandler(chatHandler, "ws/chat").setAllowedOrigins("*");
}
}
Endpoint 와 API의 차이
CORS 란?
package org.codej.websocket.controller;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.codej.websocket.domain.ChatRoom;
import org.codej.websocket.service.ChatService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/chat")
public class ChatController {
private final ChatService service;
@PostMapping
public ChatRoom createRoom(@RequestParam String name){
return service.createRoom(name);
}
@GetMapping
public List<ChatRoom> findAllRooms(){
return service.findAllRoom();
}
}
크롬 확장프로그램인talend api tester와 postman을 사용하여 roomid를 얻어와줍니다.
작동이 잘 되는 것을 알 수 있습니다.