도입
프로젝트를 진행하는데, 실시간으로 알림을 받을 수 있는 기능을 구현해야 했다.
즉, 클라이언트에서는 따로 요청 없이 서버로부터 알림 데이터를 실시간으로 받고, 나는 응답 받은 알림 데이터를 화면에 보이도록 구현해야 했다. (쉽게 말하면 알림 수신함을 구현해야 했다.)
이 방식은 SSE를 활용하면 쉽게 구현할 수 있다.
이 글에서는 SSE에 대한 개념을 설명한 뒤, 아래 예시와 같은 간단한 예제 코드를 작성해보는 시간을 가질 것이다.
React와 Node.js(express)로 구현하였으며, 각 문법을 몰라도 최대한 이해할 수 있도록 작성했다. 또한 Web API로 구현을 진행하므로 다른 라이브러리는 설치할 필요가 없다.
SSE(Server Sent Events)란?
일반적으로 클라이언트가 서버에게 요청을 보내고, 서버는 이러한 요청이 있을 때만 그에 대한 응답을 보내는 방식으로 소통이 이루어진다. 하지만 서버에서도 클라이언트의 요청 없이 자유롭게 메시지를 보낼 수 있도록 SSE Web API가 제공되고있다.
SSE(Server-Sent Events)는 실시간 웹 애플리케이션을 구현하는데 사용될 수 있는 Web API다.
SSE가 활성화된 애플리케이션은 단방향 통신, 즉 클라이언트의 요청 없이 서버 쪽에서 클라이언트에게 실시간으로 데이터를 보낼 수 있다.
이와 같은 형태는 실시간으로 소식 불러오기, 알림 받기 등 실시간으로 모니터링 해야하는 상황해서 사용할때 유용하게 사용할 수 있다.
이 과정은 HTTP를 통해서 이뤄지며, 복잡한 코딩이나 설정을 필요로하지 않는다. 동작 흐름은 다음과 같다.
- 클라이언트가 서버에게 SSE로 통신하자는 요청을 보낸다. (Connection Request)
- 서버는 이 요청을 수신하고 수락했음을 알리는 메시지를 보낸다. (Connection Response)
- 클라이언트는 이를 받고, 지금부터 서버가 보내주는 데이터들에 반응할 준비를 한다.
- 이제 서버는 정해진 이벤트가 있을 때마다 클라이언트에게 메시지를 보내게 된다. (Event Response)
이때, 클라이언트는 단방향 통신이므로 이에 응답할 수는 없다. - 클라이언트는 서버로부터 메시지가 도착할 때마다 이에 반응하여 화면을 업데이트하는 등 필요한 작업을 한다.
- 이 과정들을 하나의 연결 안에서 계속 이뤄지고, 만약 연결이 끊기면 클라이언트는 자동으로 재연결을 요청하여 통신을 재개한다.
- 필요한 작업을 마치면 클라이언트 또는 서버에서 상대방에게 종료를 통보하는 메시지를 보냄으로써 연결이 끝나게 된다. (Connection Closed)
SSE 구현 방법 - 서버
node.js 문법을 몰라도 지금은 SSE에 대해 알아가는 것이니, SSE 설정 하는 방법을 중점으로 이해하며 넘어가는 것을 권장한다.
port와 cors 설정
예시를 구현하기 위해, 클라이언트는 localhost:3001, 서버는 localhost:3000에서 실행하고 있어서 cors 처리도 해주었다.
// 3000번 포트 실행 코드
app.listen(3000, () => console.log('Server is listening on port 3000'));
// cors 허용 코드
const corsOptions = {
origin: 'http://localhost:3001', // 클라이언트 url
};
헤더 설정
서버에서 연결 요청을 처리하기 위해서 응답을 할 때는 text/event-stream형태로 받아줘야 한다.
또한 keep-alive 헤더를 실어야 하는데, 이 헤더로 클라이언트가 요청을 보내면서 만들어진 연결이 서버가 응답을 보낸 이후로도 계속 유지가 되는 것이다. 따라서 다음과 같이 헤더 설정을 해주었으며, 엔드포인트를 /events 로 설정했다.
// 헤더 설정 코드
app.get('/events', cors(corsOptions), (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
클라이언트에게 보낼 Data 작성
클라이언트와 SSE 연결이 성공하고, 클라이언트에 랜덤 숫자를 3000ms마다 보내도록 코드를 작성했다.
// send SSE
setInterval(() => {
const data = {
message: `랜덤 숫자가 도착했습니다. ${Math.floor(Math.random() * 100)} (${new Date().toLocaleString()})`,
};
console.log('sent: ', data);
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 3000);
});
전체 코드
const express = require('express');
const cors = require('cors');
const app = express();
const corsOptions = {
origin: 'http://localhost:3001',
};
app.get('/events', cors(corsOptions), (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
// send SSE
setInterval(() => {
const data = {
message: `랜덤 숫자가 도착했습니다. ${Math.floor(Math.random() * 100)} (${new Date().toLocaleString()})`,
};
console.log('sent: ', data);
res.write(`data: ${JSON.stringify(data)}\n\n`);
}, 3000);
});
app.listen(3000, () => console.log('Server is listening on port 3000'));
SSE 구현 방법 - 클라이언트
클라이언트는 SSE를 구현하기 위해 Web API인 EventSource API를 이용한다.
EventSource 인스턴스 생성하기
클라이언트가 이벤트 수신을 시작하기 위해 서버와의 연결을 열기 위해서는 먼저 EventSource 객체를 만들어야 한다. 이때, SSE 연결 처리하는 서버단 엔드포인트 주소를 포함하여 생성한다.
eventSource는 서버에서 보낸 이벤트를 수신하는 것을 대기한다.
앞서 서버는 로컬의 3000번 포트, 엔드포인트를 /events로 설정했으니 다음과 같이 작성했다.
const eventSource = new EventSource('http://localhost:3000/events');
SSE 연결 확인하기
서버와 SSE 요청이 승인이되면 onopen 가 실행된다. 이는 실행이 됐는지 파악하는 용도로 작성할 수 있다.
evtSource.onopen = function() {
console.log('SSE 연결 성공');
SetError(null);
};
연결이 끊기게 되면 Error 메시지를 갱신하도록 했는데, 재연결시 다시 null로 설정하기 위해 SetError(null) 코드도 기입했다.
message 이벤트 수신하기
클라이언트가 서버에서 보낸 메시지 이벤트를 수신하기 위해서는 message 이벤트를 위한 핸들러를 추가해야 한다.
이제 서버가 메시지를 보낼 때마다 message 이벤트인 onmessage가 실행된다.
eventSource.onmessage = (event: MessageEvent) => {
const eventData = JSON.parse(event.data);
const newNotification: Notification = {
id: Date.now(),
message: eventData.message,
};
setMessages((prevMessages) => [...prevMessages, newNotification]);
};
매개변수로 전달된 event에는 다음과 같은 값이 담겨 있다.
여기서 JSON.parse(event.data) 로 필요한 data값만을 객체로 파싱해서 가져왔다. 그리고 state 배열인 Messages에 값을 추가했다.
SSE 연결 해제
eventSource.close() 로 SSE 연결을 해제할 수 있다.
나는 React 문법인 useEffect 안에도 작성하여, 컴포넌트가 언마운트 될 때도 SSE 연결을 종료할 수 있도록 했다.
useEffect(() => {
...
return () => eventSource.close();
}, []);
또한 SSE 관련해서 문제가 발생하면 onerror 가 실행된다. 이때, eventSource.close() 를 실행시켜 SSE 연결을 끊어준다.
eventSource.onerror = (err) => {
console.error('SSE 에러:', err);
setError('실시간 알림 연결에 문제가 발생했습니다.');
eventSource.close();
};
이제 각 서버를 실행하면 앞서 보여줬던 화면을 볼 수 있을 것이다.
전체 코드
import { useEffect, useState } from 'react';
interface Notification {
id: number;
message: string;
}
function NotificationBox() {
const [messages, setMessages] = useState<Notification[]>([]);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const eventSource = new EventSource('http://localhost:3000/events');
eventSource.onopen = () => {
console.log('SSE 연결 성공');
setError(null);
};
eventSource.onmessage = (event: MessageEvent) => {
const eventData = JSON.parse(event.data);
const newNotification: Notification = {
id: Date.now(),
message: eventData.message,
};
setMessages((prevMessages) => [...prevMessages, newNotification]);
};
eventSource.onerror = (err) => {
console.error('SSE 에러:', err);
setError('실시간 알림 연결에 문제가 발생했습니다.');
eventSource.close();
};
return () => eventSource.close();
}, []);
return (
<div>
<h2>실시간 알림</h2>
{error && <p>{error}</p>}
<ul>
{messages.map((notification) => (
<li key={notification.id}>수신: {notification.message}</li>
))}
</ul>
</div>
);
}
export default NotificationBox;
유의 사항
프로젝트 환경에 따라 추가로 옵션을 작성해야하거나 다른 방식으로 작성해야할 수도 있다.
예를들어, request header에 유저 인증 토큰을 포함해야 하면 eventSource 객체 생성시 추가 옵션을 넣어야하고, 백엔드와 협의하여 event명을 커스텀으로 작성해야할 수도 있다.
일단 위 예제에서는 SSE의 기본적인 구현 방법을 바탕으로 구현한 코드이므로, 자기 프로젝트에 맞게 응용해서 개발하면 된다.
마무리
서버로부터 실시간으로 데이터를 응답받을 수 있는 SSE(Server Sent Events)에 대해 알아봤다.
또한, 간단한 예제를 통해 직접 구현해보는 시간을 가졌다.
MDN 내용을 기반으로 아래 언급한 링크들을 참고해서 기능을 구현하고 글을 작성했다.
참고 링크
https://www.youtube.com/watch?v=i4-MNzNML_c
https://www.youtube.com/watch?v=OGlLOeWVO-8
https://www.youtube.com/watch?v=-iJJANMLzbw
https://dev.to/4shub/building-with-server-sent-events-13j
https://web.dev/articles/eventsource-basics?hl=ko
https://bunny.net/academy/http/what-is-sse-server-sent-events-and-how-do-they-work/
https://youngsimi.tistory.com/entry/React-spring-react-환경에서-SSE-Server-Sent-Events-구현하기
'문제 해결 & 구현 기록' 카테고리의 다른 글
안드로이드 스튜디오 iOS 시뮬레이터 실행 에러 해결기 (1) | 2024.12.02 |
---|---|
안드로이드 스튜디오 iOS 시뮬레이터 안열림 해결 방법 (1) | 2024.12.02 |
HTML 이미지 깨짐 현상 - 파일 경로 확인하기 (0) | 2024.01.06 |
[Node.js] nvm node.js버전 변경 안됨 / exit status 5: Access is denied (0) | 2022.08.06 |
[Node.js] nvm / Node.js 버전 변경 방법 / lts로 다운그레이드 방법 / Error: error:0308010C:digital envelope routines::unsupported (0) | 2022.08.06 |