웹소켓이 사용되는 부분은 경매물품 상세 페이지에서 현재가와 입찰가 추이, 입찰 횟수 등이다. 입찰 요청까지 실시간으로 연결하여 요청을 하게 되었고, 이 부분에 대한 로직을 작성해보았다.
일단 웹소켓이란 양방향으로 통신할 수 있는 통신 프로토콜이다. 일반적인 http 프로토콜에서 서버와 클라이언트의 연결이 단방향인 것에 비해, 웹소켓은 먼저 http를 통한 handshake로 한 번 서버와 클라이언트에 연결이 일어나면 지속적인 요청과 응답이 필요없이 실시간으로 통신이 가능해진다.(연결이 종료되기 전까지.) 주로 채팅 앱이나, 주식 거래 차트 등 실시간으로 통신이 이루어져야 하는 경우 사용된다.
우리는 웹소켓을 기반으로 STOMP 프로토콜을 함께 활용하여 데이터를 주고받기로 했다.
(Streaming Text Oriented Messaging Protocol 이다.)
STOMP란 메시지 기반 프로토콜로, 메시지 브로커를 통해 데이터를 주고받을 수 있는 프로토콜이다. 브로커라는 말처럼 서버와 클라이언트 사이에서 메시지 교환을 하도록 하는 중개자 역할을 한다. 구독과 발행 (subscribe, publishing) 이라는 개념이 있어서, 1:1 로 메시징이 가능하기도 하지만 구독을 하고 있는 모든 클라이언트에 publishing 하여 메시지를 전송하는 것을 가능하도록 만든다.
또 STMOP 프로토콜을 활용하는 이유는 웹소켓의 경우 바이너리 데이터 타입으로 거의 모든 유형의 데이터 타입을 처리할 수 있다고 하는데, 이런 특징 때문에 메시지를 처리할 때 별도의 과정이 필요할 수 있다고 한다. STOMP 같은 경우 텍스트 기반이기 때문에 현재도 별도의 가공 과정을 거치고 있지는 않다. publish 같은 경우 문자열을 보내주어야 하기 때문에 JSON.stringify 정도의 메서드를 활용하고 있다.
웹소켓에 대한 내용을 공부하면서, Sockjs 라는 라이브러리를 활용하는 예제를 많이 볼 수 있었다. Sockjs 의 경우 웹소켓을 지원하지 않는 브라우저에 대응하기 위해 사용하는데, 이러한 브라우저에서는 http long-pulling 등 다른 방법을 지원한다고 한다. 근데 이 문서에 따르면 Sockjs 를 쓸 일이 요즘엔 그렇게 없다고 한다.
참고로 롱 풀링 방식이란 전통적인 http 프로토콜을 사용한 변형된 방식으로, 서로 접속된 시간을 길게 유지하여 통신하는 방법이다. 클라이언트에서 서버로 요청을 보내고, 요청에 대한 응답이 올 때까지 기다렸다가, 응답이 전송되면서 연결이 종료된다. 그리고 다시 요청을 보내서 다음 응답을 기다리는 방식으로 연결을 유지한다.
아무튼 일단은 Sockjs에 대한 필요성을 크게 느끼지 못해서 stomp.js 를 활용하여 구현을 하였다.
일단 데이터 타입을 any로 해두었는데 추후 통신테스트를 진행하면서 수정할 예정이다. 웹소켓도 로직을 분리하기 위해서 useWebsocket이라는 훅으로 만들었고, 응답데이터를 상태로 관리하여 다른 컴포넌트에 props로 전달해주려고 한다.
리액트 쿼리를 활용해서 관리하는 방법이 있는 것 같은데, 일단은 복잡하기 때문에 구현이 잘 된 뒤에 필요하다면 리팩토링을 하는 방향으로 적용할 것 같다.
import { Client } from '@stomp/stompjs';
import { useEffect, useState } from 'react';
export const useWebsocket = (subEndpoint = '/sub/board-id/2') => {
const [response, setResponse] = useState<any>({});
// 웹소켓 연결을 위한 stomp 클라이언트 생성
const client = new Client({
brokerURL: `ws:${process.env.REACT_APP_WEBSOCKET_URL}/ws`,
debug: (str) => console.log(str),
// 자동 재연결을 위한 옵션
reconnectDelay: -1,
// server와 client의 연결상태 확인을 위한 auto send 기능
heartbeatIncoming: 10000,
heartbeatOutgoing: 10000,
});
useEffect(() => {
// 구독 주소로 연결, 받은 메시지를 상태값으로 저장
client.activate();
client.onConnect = (frame) => {
console.log(`connected: ${frame}`);
client.subscribe(subEndpoint, (res) => setResponse(res.body));
};
// 컴포넌트 언마운트시 연결을 해제
return () => {
client.deactivate();
};
}, []);
const sendBid = (price: string) => {
const boardId = subEndpoint.split('/').at(-1);
const destination = '/pub/bid';
client.publish({
destination,
body: JSON.stringify({
boardId,
price,
bidderToken: '~~',
}),
});
};
return { response, sendBid };
};
이제 아래에는 요청을 위한 sendBid라는 함수가 있고, 지정된 엔드포인트로 지정된 데이터를 전송하도록 구현했다. 응답 데이터와 이 함수를 props로 전달해 원하는 컴포넌트에서 활용하고자 한다.
웹소켓이 연결되어야하는 최상위 페이지인 DetailPage이다. 훅을 불러와줌으로써 웹소켓을 연결한다.
그리고 여기서 나오는 응답 데이터를 일단 props로 전달해주었다.
데이터가 실시간으로 로그에 찍히는 것은 확인했는데, 서버측과 협의하여 렌더링 후 데이터를 바로 응답받을 수 있는 요청 엔드포인트를 하나 새로 만들어 주시기로 하였고, 아직 서버에 반영이 안된 상태여서 적용은 되지 않았다.
'프로젝트 > 데일리 옥션' 카테고리의 다른 글
# 작업일지 12,13 / STOMP publish 요청 성공! + 에러 처리는..? (0) | 2023.02.22 |
---|---|
# 작업일지 11 / 카운터 함수 드디어 고침! 문제는 useEffect에 있었다. (0) | 2023.02.22 |
# 작업일지 9 / Chart.js 적용, 경매가 유효성 검사 (0) | 2023.02.18 |
#8 작업일지 / interval함수 리팩토링, 게시글 상세페이지 통신 로직 작성 (0) | 2023.02.15 |
# 작업일지 7 / 카운트다운 함수 만들기 (0) | 2023.02.13 |