[React] SSE (Server-Sent Events) 실시간 알림, Header에 값 담기
2023. 7. 4. 23:13ㆍ개발/React
SSE를 통해서 실시간 알림을 구현해야 했다. 많은 웹사이트에서 확인 가능한 기능이며, 작은 부분이지만 구현하기는 꽤 어려웠다.
특히 서버쪽에서 헤더에 토큰을 담아서 보내달라고 하셨는데 EventSource 객체는 헤더에 접근 권한이 없기 때문에 다른 방법을 사용해야 했다. 또 컴포넌트가 랜더링 되었을때 최초 한번만 구독요청을 보내고 유지하도록 하는것이 중요했다.
SSE란
SSE(Server-Sent Events)는 클라이언트(또는 브라우저)에 메시지를 받는 방법으로, 클라이언트와 서버 간에 초기 연결이 설정되면 서버가 메시지를 클라이언트로 전달할 수 있게 된다. 이때 한 방향으로만 (서버 to 클라이언트) 작동합니다. 한번 연결이 설정되면 이벤트가 발생하면 클로즈 하기 전까지 서버에서 데이터를 넘겨준다. 따라서 Server-Sent Events는 대화형 작업이 필요하지 않은 실시간 데이터를 처리할 때 적합하며, 양방향을 계속 데이터를 주고 받는 웹 소캣 보다 실시간 알람 구현에 적합하다.
헤더에 값 담기 with EventSourcePolyfill
기존 코드
- Web API인 Server-Sent Events에서 기본으로 제공하는 EventSource 인터페이스 객체를 그대로 사용해서 구현하였다. 일반적으로 사용 가능한 방법이며, 해당 방법으로 코드를 작성할 경우 컴포넌트가 랜더링 되고나서 해당 useEffect가 실행되면서 정상적으로 코드가 실행된다.
- EventSource 인터페이스 객체를 생성할때 SSE 연결 처리를 하는 서버단 앤드포인트 주소를 포함하여 생성하여야 하는데 서버단에서 ACCESS_KEY를 검증한 다음 연결처리를 하기때문에 ACCESS_KEY가 제대로 담겨서 넘어가지 않아서 구독이 처리가 되지 않았다.
// SSE
useEffect(() => {
// 로그인 상태일때 최초 한번만 구독 실행
if(isLogin){
console.log("[SSE] 구독요청")
const eventSource = new EventSource(
`${process.env.REACT_APP_SERVER_URL}/api/subscribe`,
{
headers : {
ACCESS_KEY : getCookie('token'),
},
withCredentials: true, // 토큰 값 전달을 위해 필요한 옵션
}
)
eventSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data)
console.log("[SSE] message ", data)
// 메세지 응답 처리
})
return () => {
eventSource.close(); // 컴포넌트 언마운트 시 SSE 연결 종료
};
}
}, [isLogin]);
변경한 코드
- event-source-polyfill를 import 한 다음 EventSourcePolyfill를 EventSource로 선언을 하고 해당 이름으로 새로운 EventSource 객체를 생성한다.
- 이렇게 작성한 이유는 기존의 Server-Sent Events에서 기본으로 제공하는 EventSource 인터페이스 객체는 헤더의 접근 권한이 없기 때문에 헤더에 ACCESS_KEY를 담거나 할 수 없기 때문이다.
import { EventSourcePolyfill } from "event-source-polyfill";
/*... 중략 ...*/
useEffect(() => {
// 세션 스토리지에서 SSE 구독 상태를 확인
const isSubscribed = sessionStorage.getItem('isSubscribed');
if (isLogin && !isSubscribed) {
// 로그인 상태일때 최초 한번만 구독 실행
const subcribeSSE = async () => {
const accessKey = await getCookie('token')
const EventSource = EventSourcePolyfill
if (isLogin && accessKey && !isSubscribed) {
eventSourceRef.current = new EventSource(
//헤더에 토큰
`${process.env.REACT_APP_SERVER_URL}/sse/subscribe`,
{
headers: {
'ACCESS_KEY': accessKey,
},
withCredentials: true, // 토큰 값 전달을 위해 필요한 옵션
}
)
if (eventSourceRef.current.readyState === 1) {
// console.log("[INFO] SSE connection 상태")
}
eventSourceRef.current.addEventListener('open', (event) => {
sessionStorage.setItem('isSubscribed', true);
})
eventSourceRef.current.addEventListener('message', (event) => {
const data = event.data
if (data.indexOf('EventStream Created') === -1) {
console.log("[INFO] 알람 발생", data)
dispatcher(__alarmSender(data))
}
})
return () => {
if (eventSourceRef.current && !isLogin) {
sessionStorage.setItem('isSubscribed', false)
//dispatcher(__alarmClean())
eventSourceRef.current.close() // 로그아웃 시 SSE 연결 종료
}
};
}
};
subcribeSSE()
avataGenHandler()
}
}, [isLogin]);
EventSource 인터페이스 객체 vs EventSourcePolyfill
- 차이
- EventSource 인터페이스
- IE를 제외한 대부분의 브라우저에서 지원하며 별도의 설치가 필요하지 않다.
- 헤더에 특정 값을 담는 메소드나 속성을 제공하지 않는다.
- https://developer.mozilla.org/ko/docs/Web/API/EventSource
- EventSourcePolyfill
- IE도 지원하는 라이브러리
- 헤더에 특정 값을 담을 수 있다
- https://www.npmjs.com/package/event-source-polyfill
- EventSource 인터페이스
- EventSourcePolyfill 설치하기
npm install event-source-polyfill
yarn add event-source-polyfill
'개발 > React' 카테고리의 다른 글
[리액트] Redux ToolKit 적용하기 (0) | 2023.04.26 |
---|---|
[리액트] CRA로 리액트 프로젝트 생성 부터 Redux까지 (0) | 2023.04.26 |
[리액트] Hooks - useEffect (3) | 2023.04.25 |
[리액트] Hooks - useState (0) | 2023.04.24 |
[리액트] CSS in JS, Styled Components 란 (0) | 2023.04.17 |