Server-Sent Events (SSE) là công nghệ cho phép server push dữ liệu đến client theo thời gian thực. Tuy nhiên, trong môi trường production, kết nối mạng không phải lúc nào cũng ổn định. Bài viết này sẽ hướng dẫn bạn cách triển khai cơ chế reconnect tự động với exponential backoff sử dụng HolySheep AI — nhà cung cấp API AI với độ trễ dưới 50ms và chi phí tiết kiệm đến 85%.

Kịch bản lỗi thực tế

Tuần trước, hệ thống chatbot streaming của mình gặp lỗi nghiêm trọng lúc 3 giờ sáng. User feedback cho thấy: "Chatbot ngừng phản hồi sau vài phút tương tác". Kiểm tra logs, mình thấy hàng loạt error:

EventSource connection failed: Error: EventSource couldn't connect to server
Status: 0
URL: https://api.holysheep.ai/v1/stream/chat
Timestamp: 2024-12-20T03:15:42.123Z

--- Lỗi tiếp theo ---
Error: Connection timeout after 30000ms
Error: Failed to fetch - Network request failed
Error: 503 Service Unavailable

Nguyên nhân gốc rễ: Server HolySheep AI đang bảo trì load balancer, nhưng code của mình không có cơ chế reconnect. Chỉ cần refresh trang là mất hết conversation. Sau 3 tiếng debug, mình đã triển khai exponential backoff và giải pháp hoạt động hoàn hảo cho đến nay.

Server-Sent Events là gì?

SSE cho phép server gửi dữ liệu đến browser thông qua HTTP connection đã established. Khác với WebSocket, SSE chỉ hỗ trợ one-way communication (server → client), nhưng đơn giản hơn và hoạt động tốt qua proxies.

// Server response từ HolySheep AI (Content-Type: text/event-stream)
data: {"id":"msg_001","content":"Xin chào"}

data: {"id":"msg_002","content":"Tôi có thể"}

data: {"id":"msg_003","content":" giúp gì cho bạn?"}

data: [DONE]

Triển khai SSE Client với Exponential Backoff

1. Cấu hình cơ bản

// holySheepSSE.js
const HOLYSHEEP_CONFIG = {
    baseUrl: 'https://api.holysheep.ai/v1',
    apiKey: 'YOUR_HOLYSHEEP_API_KEY', // Thay thế bằng API key của bạn
    model: 'gpt-4.1',
    
    // Exponential backoff settings
    backoff: {
        initialDelay: 1000,    // 1 giây
        maxDelay: 30000,       // 30 giây
        multiplier: 2,
        jitter: 0.3,           // ±30% random để tránh thundering herd
        maxRetries: 10
    },
    
    // Timeout settings
    timeout: 30000            // 30 giây
};

class HolySheepSSEClient {
    constructor(config = {}) {
        this.config = { ...HOLYSHEEP_CONFIG, ...config };
        this.eventSource = null;
        this.retryCount = 0;
        this.isConnecting = false;
        this.abortController = null;
        
        // Event handlers
        this.handlers = {
            onMessage: () => {},
            onError: () => {},
            onConnect: () => {},
            onReconnecting: () => {},
            onMaxRetriesReached: () => {}
        };
    }
    
    on(event, handler) {
        if (this.handlers.hasOwnProperty(event)) {
            this.handlers[event] = handler;
        }
        return this;
    }
    
    // Tính toán delay với exponential backoff + jitter
    calculateBackoffDelay() {
        const { initialDelay, maxDelay, multiplier, jitter } = this.config.backoff;
        
        // Exponential: delay = initial * (multiplier ^ retryCount)
        let delay = initialDelay * Math.pow(multiplier, this.retryCount);
        
        // Áp dụng jitter để tránh collision
        const jitterRange = delay * jitter;
        delay = delay + (Math.random() * 2 - 1) * jitterRange;
        
        // Giới hạn max delay
        return Math.min(delay, maxDelay);
    }
    
    async connect(messages) {
        if (this.isConnecting) {
            console.warn('[HolySheepSSE] Connection already in progress');
            return;
        }
        
        this.isConnecting = true;
        this.abortController = new AbortController();
        
        try {
            const response = await fetch(${this.config.baseUrl}/chat/completions, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': Bearer ${this.config.apiKey},
                    'Accept': 'text/event-stream'
                },
                body: JSON.stringify({
                    model: this.config.model,
                    messages: messages,
                    stream: true
                }),
                signal: this.abortController.signal,
                credentials: 'include'
            });
            
            if (!response.ok) {
                const errorData = await response.json().catch(() => ({}));
                throw new Error(HTTP ${response.status}: ${errorData.error?.message || response.statusText});
            }
            
            this.retryCount = 0; // Reset retry count khi connect thành công
            this.handlers.onConnect();
            
            await this.processStream(response);
            
        } catch (error) {
            this.handleConnectionError(error);
        } finally {
            this.isConnecting = false;
        }
    }
    
    async processStream(response) {
        const reader = response.body.getReader();
        const decoder = new TextDecoder();
        let buffer = '';
        
        try {
            while (true) {
                const { done, value } = await reader.read();
                
                if (done) break;
                
                buffer += decoder.decode(value, { stream: true });
                
                // Xử lý từng dòng SSE
                const lines = buffer.split('\n');
                buffer = lines.pop() || ''; // Giữ lại dòng incompletely
                
                for (const line of lines) {
                    this.parseSSELine(line);
                }
            }
        } catch (error) {
            if (error.name !== 'AbortError') {
                this.handleConnectionError(error);
            }
        }
    }
    
    parseSSELine(line) {
        if (!line.startsWith('data: ')) return;
        
        const data = line.slice(6).trim();
        
        if (data === '[DONE]') {
            return; // Stream hoàn thành
        }
        
        try {
            const parsed = JSON.parse(data);
            
            // Xử lý theo loại event
            if (parsed.choices?.[0]?.delta?.content) {
                this.handlers.onMessage(parsed.choices[0].delta.content);
            }
        } catch (error) {
            console.warn('[HolySheepSSE] Failed to parse SSE data:', data);
        }
    }
    
    handleConnectionError(error) {
        console.error('[HolySheepSSE] Connection error:', error.message);
        
        // Kiểm tra nếu đã abort (user đóng connection)
        if (error.name === 'AbortError') {
            return;
        }
        
        this.handlers.onError(error);
        
        // Thử reconnect với exponential backoff
        if (this.retryCount < this.config.backoff.maxRetries) {
            this.scheduleReconnect();
        } else {
            this.handlers.onMaxRetriesReached();
        }
    }
    
    scheduleReconnect() {
        this.retryCount++;
        const delay = this.calculateBackoffDelay();
        
        console.log([HolySheepSSE] Scheduling reconnect #${this.retryCount} in ${Math.round(delay)}ms);
        this.handlers.onReconnecting({
            retryCount: this.retryCount,
            delay: delay,
            error: null
        });
        
        this.reconnectTimeout = setTimeout(() => {
            console.log([HolySheepSSE] Attempting reconnect #${this.retryCount});
            // Lưu ý: Trong thực tế cần lưu messages để gửi lại
            // this.connect(this.lastMessages);
        }, delay);
    }
    
    disconnect() {
        if (this.reconnectTimeout) {
            clearTimeout(this.reconnectTimeout);
        }
        if (this.abortController) {
            this.abortController.abort();
        }
        this.isConnecting = false;
        console.log('[HolySheepSSE] Disconnected');
    }
}

export default HolySheepSSEClient;

2. React Hook cho SSE Integration

// useHolySheepSSE.js
import { useState, useEffect, useRef, useCallback } from 'react';
import HolySheepSSEClient from './holySheepSSE';

export function useHolySheepSSE(apiKey) {
    const [messages, setMessages] = useState([]);
    const [isConnected, setIsConnected] = useState(false);
    const [isReconnecting, setIsReconnecting] = useState(false);
    const [error, setError] = useState(null);
    const [retryInfo, setRetryInfo] = useState(null);
    
    const clientRef = useRef(null);
    const conversationHistoryRef = useRef([]);
    
    // Khởi tạo client
    useEffect(() => {
        clientRef.current = new HolySheepSSEClient({
            apiKey: apiKey,
            backoff: {
                initialDelay: 1000,
                maxDelay: 30000,
                multiplier: 2,
                jitter: 0.3,
                maxRetries: 10
            }
        });
        
        clientRef.current
            .on('onConnect', () => {
                setIsConnected(true);
                setIsReconnecting(false);
                setError(null);
                console.log('[Hook] Connected to HolySheep AI');
            })
            .on('onMessage', (content) => {
                setMessages(prev => {
                    const lastMessage = prev[prev.length - 1];
                    if (lastMessage?.role === 'assistant') {
                        // Append vào message cuối
                        return [
                            ...prev.slice(0, -1),
                            { ...lastMessage, content: lastMessage.content + content }
                        ];
                    }
                    // Tạo message mới
                    return [...prev, { role: 'assistant', content }];
                });
            })
            .on('onError', (err) => {
                setError(err);
                console.error('[Hook] Error:', err.message);
            })
            .on('onReconnecting', (info) => {
                setIsReconnecting(true);
                setRetryInfo(info);
                console.log([Hook] Reconnecting #${info.retryCount} in ${Math.round(info.delay)}ms);
            })
            .on('onMaxRetriesReached', () => {
                setError(new Error('Đã thử kết nối tối đa số lần. Vui lòng kiểm tra kết nối mạng.'));
                setIsReconnecting(false);
            });
        
        return () => {
            if (clientRef.current) {
                clientRef.current.disconnect();
            }
        };
    }, [apiKey]);
    
    const sendMessage = useCallback(async (userMessage) => {
        if (!clientRef.current) {
            throw new Error('Client chưa được khởi tạo');
        }
        
        // Thêm user message vào history
        conversationHistoryRef.current = [
            ...conversationHistoryRef.current,
            { role: 'user', content: userMessage }
        ];
        
        // Hiển thị user message
        setMessages(prev => [...prev, { role: 'user', content: userMessage }]);
        
        // Kết nối và stream response
        await clientRef.current.connect(conversationHistoryRef.current);
        
    }, []);
    
    const disconnect = useCallback(() => {
        if (clientRef.current) {
            clientRef.current.disconnect();
            setIsConnected(false);
        }
    }, []);
    
    const clearMessages = useCallback(() => {
        setMessages([]);
        conversationHistoryRef.current = [];
    }, []);
    
    return {
        messages,
        isConnected,
        isReconnecting,
        error,
        retryInfo,
        sendMessage,
        disconnect,
        clearMessages
    };
}

// Component Demo
export function ChatDemo() {
    const [input, setInput] = useState('');
    const {
        messages,
        isConnected,
        isReconnecting,
        error,
        retryInfo,
        sendMessage,
        clearMessages
    } = useHolySheepSSE('YOUR_HOLYSHEEP_API_KEY');
    
    const handleSubmit = async (e) => {
        e.preventDefault();
        if (!input.trim() || !isConnected) return;
        
        await sendMessage(input);
        setInput('');
    };
    
    return (
        <div className="chat-container">
            <div className="messages">
                {messages.map((msg, idx) => (
                    <div key={idx} className={message ${msg.role}}>
                        {msg.content}
                    </div>
                ))}
                {isReconnecting && (
                    <div className="reconnecting">
                        🔄 Đang kết nối lại ({retryInfo?.retryCount})...
                        <small>Dự kiến {Math.round(retryInfo?.delay / 1000)}s</small>
                    </div>
                )}
            </div>
            
            {error && <div className="error">{error.message}</div>}
            
            <form onSubmit={handleSubmit}>
                <input
                    value={input}
                    onChange={(e) => setInput(e.target.value)}
                    placeholder={isConnected ? "Nhập tin nhắn..." : "Đang kết nối..."}
                    disabled={!isConnected}
                />
                <button type="submit" disabled={!isConnected}>Gửi</button>
            </form>
        </div>
    );
}

3. React Native Version

Đối với ứng dụng mobile, mình khuyên dùng thư viện react-native-event-source vì EventSource native có performance tốt hơn:

// HolySheepSSEReactNative.js
import EventSource from 'react-native-event-source';

class HolySheepSSEReactNative {
    constructor(config) {
        this.config = config;
        this.eventSource = null;
        this.retryCount = 0;
        this.listeners = {
            onMessage: () => {},
            onError: () => {},
            onOpen: () => {},
            onReconnecting: () => {}
        };
        
        // Exponential backoff state
        this.backoffState = {
            initialDelay: 1000,
            maxDelay: 30000,
            multiplier: 2,
            jitter: 0.3,
            maxRetries: 10
        };
    }
    
    calculateDelay() {
        let delay = this.backoffState.initialDelay * 
            Math.pow(this.backoffState.multiplier, this.retryCount);
        
        // Apply jitter
        const jitter = delay * this.backoffState.jitter;
        delay += (Math.random() * 2 - 1) * jitter;
        
        return Math.min(delay, this.backoffState.maxDelay);
    }
    
    connect(messages) {
        const url = new URL(${this.config.baseUrl}/chat/completions);
        
        const eventSource = new EventSource(url.toString(), {
            headers: {
                'Content-Type': 'application/json',
                'Authorization': Bearer ${this.config.apiKey}
            },
            body: JSON.stringify({
                model: this.config.model,
                messages: messages,
                stream: true
            }),
            method: 'POST'
        });
        
        eventSource
            .onopen(() => {
                this.retryCount = 0;
                this.listeners.onOpen();
                console.log('[ReactNative] SSE Connected');
            })
            .onmessage((event) => {
                try {
                    const data = JSON.parse(event.data);
                    if (data.choices?.[0]?.delta?.content) {
                        this.listeners.onMessage(data.choices[0].delta.content);
                    }
                } catch (error) {
                    console.warn('[ReactNative] Parse error:', error);
                }
            })
            .onerror((error) => {
                console.error('[ReactNative] SSE Error:', error);
                eventSource.close();
                
                if (this.retryCount < this.backoffState.maxRetries) {
                    this.scheduleReconnect();
                } else {
                    this.listeners.onError(new Error('Max retries reached'));
                }
            });
        
        this.eventSource = eventSource;
        return this;
    }
    
    scheduleReconnect() {
        this.retryCount++;
        const delay = this.calculateDelay();
        
        console.log([ReactNative] Reconnecting in ${Math.round(delay)}ms);
        this.listeners.onReconnecting({
            retryCount: this.retryCount,
            delay: delay
        });
        
        setTimeout(() => {
            // Implement reconnect logic với messages đã lưu
            // this.connect(this.savedMessages);
        }, delay);
    }
    
    disconnect() {
        if (this.eventSource) {
            this.eventSource.close();
            this.eventSource = null;
        }
    }
    
    on(event, handler) {
        if (this.listeners.hasOwnProperty(event)) {
            this.listeners[event] = handler;
        }
        return this;
    }
}

export default HolySheepSSEReactNative;

Tại sao chọn HolySheep AI cho SSE Streaming?

Trong quá trình benchmark, mình so sánh HolySheep AI với các provider khác và phát hiện một số điểm nổi bật:

Lỗi thường gặp và cách khắc phục

1. Lỗi CORS khi sử dụng SSE

// ❌ Lỗi: CORS policy blocked
Access to fetch at 'https://api.holysheep.ai/v1/chat/completions' 
from origin 'https://yourdomain.com' has been blocked by CORS policy

// ✅ Khắc phục: Thêm headers đúng cách
const response = await fetch(${this.config.baseUrl}/chat/completions, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'Authorization': Bearer ${this.config.apiKey},
        'Accept': 'text/event-stream',
        'Access-Control-Allow-Origin': '*' // Backend cần hỗ trợ
    },
    // ...
});

Nguyên nhân: Browser chặn cross-origin request nếu server không trả về headers CORS phù hợp.

Giải pháp: Đảm bảo HolySheep AI endpoint trả về:

Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: Content-Type, Authorization, Accept

Tài nguyên liên quan

Bài viết liên quan