라이브러리 설치
React Native 환경에서 Server-Sent Events(SSE)를 처리하기 위해 react-native-sse 패키지를 사용합니다. 이 라이브러리는 웹 브라우저의 표준 EventSource API를 모방하여 네이티브 앱에서도 스트리밍 데이터를 손쉽게 수신할 수 있도록 지원합니다.
npm install react-native-sse
SSE 연결 및 이벤트 처리 기본 구조
모듈을 불러온 후, SSE 서버의 URL을 인자로 전달하여 인스턴스를 생성합니다. 이어서 open, error, close 이벤트에 대한 리스너를 등록하여 연결 상태를 관리합니다.
import EventSource from 'react-native-sse';
const url = 'https://example.com/stream-endpoint';
const eventSource = new EventSource(url);
// 연결 성공 시
eventSource.addEventListener('open', (e) => {
console.log('SSE 연결이 수립되었습니다.');
});
// 메시지 수신 시
eventSource.addEventListener('message', (e) => {
console.log('수신된 데이터:', e.data);
});
// 에러 발생 시
eventSource.addEventListener('error', (e) => {
if (e.type === 'error') {
console.error('네트워크 오류 발생:', e.message);
} else if (e.type === 'exception') {
console.error('예외 발생:', e.error);
}
});
// 연결 종료 시
eventSource.addEventListener('close', (e) => {
console.log('SSE 연결이 해제되었습니다.');
});
상세 설정 및 옵션
EventSource 생성자의 두 번째 인자를 통해 요청 방식, 헤더, 타임아웃 등 다양한 옵션을 구성할 수 있습니다. 특히 인증이 필요한 API나 POST 방식의 요청을 보낼 때 유용합니다.
const configOptions = {
method: 'GET', // HTTP 요청 방식 (기본값: GET)
headers: {
'Authorization': 'Bearer YOUR_TOKEN'
},
body: undefined, // 요청 본문 (POST 요청 시 사용)
timeout: 0, // 연결 유지 시간 (0은 무제한)
pollingInterval: 5000, // 재연결 시도 간격 (ms)
withCredentials: false, // CORS 요청 시 쿠키 포함 여부
debug: false, // 디버깅 로그 활성화
};
const source = new EventSource(url, configOptions);
실전 예제: OpenAI API 스트리밍 채팅
OpenAI의 Chat Completion API는 스트리밍 응답을 지원합니다. react-native-sse를 사용하여 실시간으로 들어오는 토큰을 화면에 표시하는 컴포넌트 예제입니다.
import React, { useEffect, useState } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import EventSource from 'react-native-sse';
const OPENAI_API_KEY = 'YOUR_OPENAI_API_KEY';
const ChatStreamScreen = () => {
const [responseText, setResponseText] = useState('응답 대기 중...');
useEffect(() => {
const apiUrl = 'https://api.openai.com/v1/chat/completions';
// 요청 본문 구성
const requestBody = JSON.stringify({
model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: 'React Native에 대해 간단히 설명해줘.' }],
stream: true, // 스트리밍 모드 활성화
});
const sseConnection = new EventSource(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${OPENAI_API_KEY}`,
},
body: requestBody,
pollingInterval: 0, // 수동 종료 시 재연결 방지
});
sseConnection.addEventListener('open', () => {
setResponseText(''); // 텍스트 초기화
});
sseConnection.addEventListener('message', (event) => {
if (event.data === '[DONE]') {
sseConnection.close();
return;
}
try {
const parsedData = JSON.parse(event.data);
const contentChunk = parsedData.choices?.[0]?.delta?.content;
if (contentChunk) {
setResponseText((prev) => prev + contentChunk);
}
} catch (err) {
console.warn('데이터 파싱 오류');
}
});
sseConnection.addEventListener('error', (err) => {
console.error('스트리밍 에러:', err);
});
// 컴포넌트 언마운트 시 연결 정리
return () => {
sseConnection.removeAllEventListeners();
sseConnection.close();
};
}, []);
return (
<View style={styles.container}>
<Text style={styles.textStyle}>{responseText}</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
padding: 20,
},
textStyle: {
fontSize: 16,
lineHeight: 24,
},
});
export default ChatStreamScreen;