Stomp 동작 방식
왜 웹소켓을 사용할까?
웹소켓을 사용하지 않을 때의 일반적인 CRUD 과정을 생각해봅시다. url 을 생각해보면 대략적으로 게시글 리스트, 게시글 세부내용, 게시글 작성, 게시글 삭제에 대한 url 이 존재하겠죠?
하지만 여러사람이 한 페이지에서 위의 동작을 하는 경우를 생각해봅시다. 보는것은 여러사람이 같은 페이지를 볼 수 있지만, 만약 그 페이지 에서 데이터 수정이 이루어진다면 어떻게 될까요?
•
A 열심히 글 작성중
•
B 같은 페이지에 들어옴 (여기까지는 문제가 되지 않습니다.)
•
B 글 작성했으니까 완료버튼 눌러야게따~
•
A ..? 작성한글 어디로..?
리로드가 되버리면 A 의 데이터는 어디로 갈까요? 자동저장 기능이 없다면 유실됩니다. 만약 자동 리로드를 하지 않도록 한다면 A 와 B 둘 다 데이터 유실은 되지 않겠지만 새로고침을 하기 전까지는 내용 갱신이 되지 않습니다. 이는 웹소켓과 일반적인 http 통신의 차이 때문입니다.
아래 모식도를 보면 훨씬 이해하기 편할 거에요
•
http 통신의 구조
•
WebSocket 통신의 구조
•
둘의 구조 차이
특정 유저의 플래너를 방문할 때의 url 은 어떻게 되나?
백단에서 아래와 같이 Controller 를 작성해 주었습니다.
@GetMapping("detail")
public String plannerDetail(Long tpIdx, Model model) {
PlannerDetailResponse dto = plannerService.findPlannerBytpIdx(tpIdx);
model.addAttribute("planner", dto);
Member member = new Member();
Profile profile = profileService.getProfileData(UserPrincipal.getUserPrincipal().getPrincipal().getUserId());
model.addAttribute("profile", profile);
return "planner/planner1";
}
Java
복사
@RequestMapping 은 planner 이므로 특정 유저의 플래너를 방문할 때에는 최종 url 이 아래와 같아집니다.
소켓은 어떻게 열리는걸까?
바닐라 자바스크립트와 타임리프를 이용하였습니다.
백단에서의 기본적인 설정은 마치고, 소켓이 열려야 하는 html 파일에 아래와 같이 작성하면 소켓이 열립니다.
var stompClient = null;
function connect() {
// 새로운 소켓 생성
var socket = new SockJS('/gs-guide-websocket');
stompClient= Stomp.over(socket);
// 소켓 통신의 시작
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
...
JavaScript
복사
그러면 위의 url 에 방문한 모든 사람들과 1:N 연결이 됩니다. (일반적인 http 통신은 1:1 연결방식 입니다.)
MessageBroker 는 어떻게 지정 / 매핑 되는가?
백엔드에서 설정한 웹소켓 설정을 보면 알 수 있습니다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
/*
destination 에 /app/ 이 앞에 붙으면 아 얘는 메세지를 전달해주는 애구나
하고 생각하면 될 것 같습니다.
destination 에 /topic/ 이 앞에 붙으면 아 이곳에 처리된 데이터가 오는 구나
하고 생각하면 될 것 같습니다.
*/
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
// 새로운 소켓 생성시 필요한 엔드포인트 입니다.
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
Java
복사
이제 다시 화면으로 돌아가서 보겠습니다! 이제 메세지 브로커와 객체를 추가해보겠습니다.
var stompClient = null;
function connect() {
// 새로운 소켓 생성
var socket = new SockJS('/gs-guide-websocket');
stompClient= Stomp.over(socket);
// 소켓 통신의 시작
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
// 메세지 브로커와 객체 추가
stompClient.subscribe('/topic/planner-message/' + [[${planner.tpIdx}]], function (recommand) {
res = JSON.parse(recommand.body);
console.log('res: ' + res);
Java
복사
이렇게 작성해주면 아래와 같은 내용이 콘솔에 찍히게 됩니다.
이 destination 을 통해 백단과 화면이 소통을 하게 됩니다.
화면에서 받은 데이터를 뒤에서 처리한 뒤, 다시 보내준다!
여행 날짜를 한번 생성해보겠습니다!
다음과 같이 날짜를 생성했을 때 개발자 도구를 열어보면 아래와 같은 내용을 확인할 수 있습니다.
위의 내용은 어떻게 나온건지 백단에서부터 보겠습니다.
@Controller
@AllArgsConstructor
@RequestMapping("todolist")
public class TodoListController {
private final TodoListService todoListService;
private final SimpMessagingTemplate simpMessagingTemplate;
// 메세지 브로커(/app/upload-todolist/{tpIdx}) 와 매핑을 합니다.
@MessageMapping("/upload-todolist/{tpIdx}")
public void upload(
@DestinationVariable("tpIdx") Long tpIdx,
TodoListRegistRequest dto
) throws Exception {
/*
괄호 안의 destination 을 SUBSCRIBE 하고 있는 모든 사람들에게
앞에서 보내 처리된 데이터를 보내겠다는 뜻입니다.
*/
simpMessagingTemplate.convertAndSend("/topic/planner-message/" + tpIdx,
Map.of("type","upload-todolist","msg",todoListService.createTodoList(dto, tpIdx)));
}
...
Java
복사
그럼 보낸 데이터는 어떻게 받을까요? 앞단의 코드를 확인해봅시당.
var stompClient = null;
// 투두리스트 생성 시 만들어져야하는 동적 요소와 백단에서의 데이터 받기
function connect() {
// 새로운 소켓 생성
var socket = new SockJS('/gs-guide-websocket');
stompClient= Stomp.over(socket);
// 소켓 통신의 시작
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
// 백단에선 처리된 데이터 받을 때 <<< MESSAGE 부분
stompClient.subscribe('/topic/planner-message/' + [[${planner.tpIdx}]], function (recommand) {
// 백단에서 보낸 객체를 res 로 지정했습니다.
res = JSON.parse(recommand.body);
console.log('res: ' + res);
/*
뒤에서 type 과 데이터를 바인딩 했기 때문에 조건문으로 구분해주었습니다.
if 아래의 내용은 요소의 동적생성을 위한 코드이기 때문에 무시해도 됩니다.
콘솔로 res 안에 어떤 데이터가 담겨있는지 확인하고, 사용했습니다.
*/
if (res.type === 'upload-todolist') {
// 동적으로 생성되어야 할 요소들을 넣었습니다.
})
...
}
// 투두 리스트 추가
function addTodoList() {
let title = todolistTitleAdd.value;
console.dir(title);
// 메세지 브로커 입니다. 백단의 @MassageMapping 과 매핑됩니다.
stompClient.send("/app/upload-todolist/" + [[${planner.tpIdx}]], {}, JSON.stringify({'title':title}));
}
Java
복사