안녕하세요, 개발자 여러분. 저는 HolySheep AI에서 실제 서비스를 운영하는 엔지니어입니다. 이번 글에서는 AI 스트리밍 API에서 가장 중요한 WebSocket 긴 연결 관리를 초보자도 이해할 수 있도록 쉽게 설명드리겠습니다.

WebSocket이 뭔가요? 왜 필요한가요?

여러분이 AI 채팅 앱을 만든다고 상상해보세요. AI가 한 글자씩 타이핑하듯 답변을 보여주려면 어떻게 해야 할까요?

기존 방식 (REST API): 질문 → 서버가 전체 답변 완성 → 한 번에 전송 → 사용자는 답변이 끝날 때까지 기다려야 합니다.

WebSocket 방식: 질문 → AI가 답변 작성 중 → 한 글자씩 실시간 전송 → 사용자가 바로바로 답변을 확인할 수 있습니다.

AI 스트리밍 API의 핵심이 바로 이 실시간 데이터 흐름입니다. HolySheep AI는 이러한 스트리밍 연결을 안정적으로 관리해주는 게이트웨이 서비스를 제공합니다.

긴 연결(Long Connection)이란?

브라우저와 서버가 지속적으로 연결된 상태를 말합니다. 일반 HTTP는 요청-응답 후 연결이 끊기지만, WebSocket은:

Node.js로 HolySheep AI 스트리밍 API 연결하기

먼저 프로젝트 폴더를 만들고 필요한 도구를 설치합니다.

mkdir ai-streaming-demo
cd ai-streaming-demo
npm init -y
npm install ws dotenv

프로젝트 구조는 다음과 같이 됩니다:

ai-streaming-demo/
├── .env              # API 키 저장
├── server.js         # WebSocket 서버
└── package.json     # 프로젝트 설정

.env 파일에 HolySheep AI API 키를 저장합니다.

# .env 파일
HOLYSHEEP_API_KEY=YOUR_HOLYSHEEP_API_KEY

완전한 스트리밍 API 서버 코드

// server.js
const WebSocket = require('ws');
require('dotenv').config();

// HolySheep AI 스트리밍 엔드포인트
const HOLYSHEEP_WS_URL = 'wss://api.holysheep.ai/v1/chat/completions';

const apiKey = process.env.HOLYSHEEP_API_KEY;

// 클라이언트 연결 관리 맵
const clientConnections = new Map();
let connectionId = 0;

// WebSocket 서버 생성 (포트 8080)
const wss = new WebSocket.Server({ port: 8080 });

console.log('🚀 AI 스트리밍 서버가 포트 8080에서 실행 중입니다.');
console.log('📡 HolySheep AI 게이트웨이 연결 대기 중...');

wss.on('connection', (clientWs, req) => {
    const clientId = ++connectionId;
    clientConnections.set(clientId, {
        ws: clientWs,
        connectedAt: Date.now(),
        messageCount: 0
    });

    console.log(✅ 클라이언트 ${clientId} 연결됨 (총 ${clientConnections.size}개 연결));

    // 클라이언트에게서 메시지 수신
    clientWs.on('message', async (message) => {
        try {
            const data = JSON.parse(message);
            console.log(📨 클라이언트 ${clientId}: ${JSON.stringify(data).substring(0, 100)}...);

            // HolySheep AI로 스트리밍 요청
            const response = await fetch(HOLYSHEEP_WS_URL, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': Bearer ${apiKey}
                },
                body: JSON.stringify({
                    model: 'gpt-4.1',
                    messages: data.messages || [
                        { role: 'user', content: data.prompt || '안녕하세요' }
                    ],
                    stream: true
                })
            });

            if (!response.ok) {
                throw new Error(HolySheep API 오류: ${response.status});
            }

            // 스트리밍 응답을 클라이언트에게 전달
            const reader = response.body.getReader();
            const decoder = new TextDecoder();

            while (true) {
                const { done, value } = await reader.read();
                if (done) break;

                const chunk = decoder.decode(value);
                const lines = chunk.split('\n');

                for (const line of lines) {
                    if (line.startsWith('data: ')) {
                        const jsonStr = line.slice(6);
                        if (jsonStr === '[DONE]') {
                            clientWs.send(JSON.stringify({ type: 'done' }));
                        } else {
                            try {
                                const parsed = JSON.parse(jsonStr);
                                clientWs.send(JSON.stringify(parsed));
                                clientConnections.get(clientId).messageCount++;
                            } catch (e) {
                                // 파싱 오류 무시 (하트비트 등)
                            }
                        }
                    }
                }
            }

            console.log(📤 클라이언트 ${clientId}: 스트리밍 완료 (총 ${clientConnections.get(clientId)?.messageCount}개 청크));

        } catch (error) {
            console.error(❌ 클라이언트 ${clientId} 오류:, error.message);
            clientWs.send(JSON.stringify({ error: error.message }));
        }
    });

    // 클라이언트 연결 해제
    clientWs.on('close', () => {
        clientConnections.delete(clientId);
        console.log(🔌 클라이언트 ${clientId} 연결 해제됨 (남은 연결: ${clientConnections.size}개));
    });

    // 에러 처리
    clientWs.on('error', (error) => {
        console.error(⚠️ 클라이언트 ${clientId} 소켓 에러:, error.message);
        clientConnections.delete(clientId);
    });
});

// 주기적 하트비트 및 연결 상태 모니터링
setInterval(() => {
    const now = Date.now();
    let activeConnections = 0;
    let staleConnections = 0;

    clientConnections.forEach((conn, id) => {
        const age = now - conn.connectedAt;
        if (age > 300000) { // 5분 이상 방치된 연결
            console.log(⏰ 클라이언트 ${id} 연결 타임아웃 처리 (${Math.floor(age/1000)}초));
            conn.ws.terminate();
            clientConnections.delete(id);
            staleConnections++;
        } else {
            activeConnections++;
            // Ping 전송으로 연결 유지
            if (conn.ws.readyState === WebSocket.OPEN) {
                conn.ws.ping();
            }
        }
    });

    if (activeConnections > 0 || staleConnections > 0) {
        console.log(📊 연결 상태: 활성 ${activeConnections}개, 정리됨 ${staleConnections}개);
    }
}, 60000);

// 서버 종료 처리
process.on('