그 동안 사용해왔던 api 호출은 요청/응답 형식 중심으로 진행되었는데,
이번에 구현해야 할 로직은 실시간으로 데이터를 받아오는 형식으로 해야했다.
이런 경우, 적용 가능한 것을 찾아보니 Server-Sent Events(SSE)
라는 것을 알게 되었다.
개발 전 웹소켓(WebSocket
)과 SSE
중 어느 것을 적용할 지 논의되었는데
웹소켓은 인프라쪽도 변경(프로토콜을 처리하기 위해 전이중 연결과 새로운 웹 소켓 서버가 필요)이 되어야 해서
SSE를 적용하기로 결졍되어 개발하면서 알게 된 SSE를 기록해본다
Server-Sent Events(SSE)
란?EventSource - Web API | MDN
Server Sent Events
💡
Server-Sent Events(SSE)
란, 서버의 데이터를 실시간으로, 지속적으로 streaming 하는 기술.
polling(client pull) | webSocket(server push) | sse(server push) |
---|---|---|
클라이언트가 일정한 주기로 서버에 업데이트 요청을 보내는 방법. 지속적인 HTTP 요청이 발생하기 때문에 리소스 낭비가 발생한다 | 실시간 양방향 데이터 통신을 위한 스펙으로 서버와 브라우저가 지속적으로 연결된 TCP라인을 통해 실시간 데이터를 주고받을 수 있도록 하는 HTML5 사양이다. 연결지향 양방향 통신이 가능하며 채팅, 게임, 주식 차트 등에 사용된다. polling은 주기적으로 HTTP 요청을 수행하지만 webSocket은 연결을 유지하여 서버와 클라이언트 간 양방향 통신이 가능하다. | 이벤트가 서버→ 클라이언트 방향으로만 흐르는 단방향 통신 채널이다. SSE는 클라이언트가 polling과 같이 주기적으로 HTTP 요청을 보낼 일 없이 HTTP 연결을 통해 서버에서 클라이언트로 데이터를 보낼 수 있다 |
eventSource.close()
적용해야 했는데onerror
이벤트가 에러 뿐만 아니라 종료 시점도 된다는 것을 나중에 알게 돼서 이때 좀 어려움을 겪었다..😰const fetchSSE = () => {
const eventSource = new EventSource(url);
eventSource.onopen = () => {
// 연결 시 할 일
};
eventSource.onmessage = async (e) => {
const res = await e.data;
const parsedData = JSON.parse(res);
// 받아오는 data로 할 일
};
eventSource.onerror = (e: any) => {
// 종료 또는 에러 발생 시 할 일
eventSource.close();
if (e.error) {
// 에러 발생 시 할 일
}
if (e.target.readyState === EventSource.CLOSED) {
// 종료 시 할 일
}
};
};
token
을 보내야 했다. 그런데 EventSource
는 지원하지 않는다..😰EventSourcePolyfill
라는 것을 알게 되었다.npm install event-source-polyfill
// 또는
yarn add event-source-polyfill
import { EventSourcePolyfill } from 'event-source-polyfill';
const fetchSSE = () => {
const eventSource = new EventSourcePolyfill(url, {
headers: {
token: authToken,
},
});
// 생략.. 동일..
};
위의 방법은 GET
메서드만 지원하는데, 요청 작업에서 POST
메서드로 변경해야 하는 일이 생겼다
GET
으로 처리가 가능했는데POST
로 변경해서 body에 보내야 했다.서버는 node.js 환경이라 axios
로 가능했는데 브라우저에서 불가능해서 fetch
를 사용해서 구현했다
const fetchSSE = () => {
fetch(url, {
method: 'POST',
headers: {
token: authToken,
'Content-Type': 'application/json; charset=utf-8',
},
body: {
// 생략
},
})
.then((response) => {
const reader = response.body!.getReader();
const decoder = new TextDecoder();
const readChunk = () => {
return reader.read().then(appendChunks);
};
const appendChunks = (result) => {
const chunk = decoder.decode(result.value || new Uint8Array(), {
stream: !result.done,
});
const parseData = JSON.parse(chunk);
// 받아오는 data로 할 일
if (!result.done) {
return readChunk();
}
};
return readChunk();
})
.then(() => {
// 종료 시 할 일
})
.catch((e) => {
// 에러 처리
});
};