פיתוח Server-Sent Events (SSE) לאפליקציות ווב

כאשר הנתונים באתר מתיישנים, המשתמשים נאלצים לרענן את הדף ידנית או שהמערכת צריכה לשאול את השרת עשרות פעמים בדקה. זה מעמיס על התשתית ופוגע בחוויית המשתמש. אנו מפתחים Server-Sent Events (SSE) — טכנולוגיה שמספקת עדכונים מיידית, בזמן אמת, ללא בקשות מיותרות. הצוות שלנו מיישם פתרון מלא, המבטיח פעולה אמינה ותמיכה מתמשכת בכל השלבים.

פיתוח ותחזוקה של כל סוגי האתרים:

אתרי מידע או יישומי אינטרנט
אתרי תדמית, דפי נחיתה, אתרי חברה, קטלוגים מקוונים, חידונים, אתרי קידום, בלוגים, מקורות חדשות, פורטלי מידע, פורומים, אגרגטורים
אתרי מסחר אלקטרוני או יישומי אינטרנט
חנויות מקוונות, פורטלי B2B, שווקים, בורסות מקוונות, אתרי קאשבק, בורסות, פלטפורמות דרופשיפינג, מנתחי מוצרים
יישומי אינטרנט לניהול תהליכים עסקיים
מערכות CRM, מערכות ERP, פורטלים ארגוניים, מערכות ניהול ייצור, מנתחי מידע
אתרי שירות אלקטרוני או יישומי אינטרנט
פלטפורמות מודעות, בתי ספר מקוונים, בתי קולנוע מקוונים, בוני אתרים, פורטלים לשירותים אלקטרוניים, פלטפורמות אירוח וידאו, פורטלים נושאיים

אלה רק חלק מהסוגים הטכניים של אתרים שאנו עובדים איתם, ולכל אחד מהם יכולים להיות מאפיינים ופונקציונליות ספציפיים משלו, וכן ניתן להתאים אותם לצרכים ולמטרות הספציפיים של הלקוח.

השירותים שאנו מציעים
מציג 1 מתוך 1כל 2062 השירותים
פיתוח Server-Sent Events (SSE) לאפליקציות ווב
בינוני
מ- 1 יום עד 3 ימים

הכישורים שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אתר חברה B2B ADVANCE
    פיתוח אתר חברה B2B ADVANCE
    1502
  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1344
  • פיתוח אתר עבור BELFINGROUP
    פיתוח אתר עבור BELFINGROUP
    1052
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1307
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1049
  • פיתוח אתר לחברת FIXPER
    פיתוח אתר לחברת FIXPER
    1033

בעת עדכון סטטוס ההזמנה, הלקוח נאלץ לטעון מחדש את העמוד או לשאול את ה-API כל 5 שניות. זה מגביר את העומס על השרת ופוגע בחוויית המשתמש. מצב דומה מתרחש בפלטפורמה פיננסית שבה השערים משתנים כל שנייה—WebSocket הוא מוגזם, אבל שאילתות קבועות הורגות את מסד הנתונים. אנו פותרים בעיה זו עם Server-Sent Events (SSE)—טכנולוגיית סטרימינג חד-כיוונית מהשרת ללקוח דרך HTTP. הניסיון שלנו מראה: SSE מפחית את מספר הבקשות פי 10 ומספק אירועים תוך <1 שנייה.

למה SSE ולא WebSocket?

קריטריון SSE WebSocket
כיוון חד-כיווני (שרת→לקוח) דו-כיווני
תאימות HTTP HTTP/1.1 טבעי דורש שדרוג
חיבור מחדש אוטומטי מובנה דורש יישום
תאימות פרוקסי עובד ללא בעיות לעיתים קרובות חסום
CORS GET ללא preflight Preflight עבור non-GET

SSE מתאים יותר למשימות שבהן הודעות ועדכונים חיים חשובים. שליחת נתונים מהלקוח לשרת היא נדירה. הגדרת SSE אורכת בממוצע חצי מהזמן בהשוואה ל-WebSocket. לדוגמה, עבור הודעות סטטוס הזמנה SSE הוא אידיאלי, בעוד שעבור צ'אט WebSocket עדיף.

איך SSE מתקנה על פני מספר שרתים?

בפרויקט אחד למסחר אלקטרוני גדול, יישמנו SSE עם הפצה מבוזרת דרך Redis Pub/Sub. הלקוח השיג הפחתה בזמן עדכון סטטוס ההזמנה מ-5 דקות לשנייה אחת. המערכת מתמודדת עם 10,000 חיבורים מקבילים ללא אובדן אירועים. התקנה מושגת דרך ברוקר הודעות: כל השרתים נרשמים לערוץ משותף ומפרסמים אירועים לכל הלקוחות. השתמשנו בתבנית זו ביותר מ-30 פרויקטים בשנים האחרונות.

יישום טכני

פורמט SSE

תגובת השרת היא text/event-stream עם שדות data:, event:, id:, retry::

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

data: {"type":"notification","message":"Новый заказ"}
event: order_update
data: {"orderId":"123","status":"shipped"}
id: msg_456
retry: 3000
: comment (игнорируется клиентом)

נקודת קצה בשרת (Node.js + Express)

קוד מלא של נקודת הקצה בשרת עם heartbeat ורישום לקוחות
app.get('/api/events', (req, res) => {
  const userId = req.user.id;
  res.writeHead(200, {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive',
    'X-Accel-Buffering': 'no', // для Nginx: отключить буферизацию
  });
  // Немедленно отправляем первый пакет (обход nginx буферизации)
  res.write(':ok\n\n');
  // Добавляем клиента в реестр
  const clientId = nanoid();
  clients.set(clientId, { res, userId });
  // Heartbeat каждые 15 секунд
  const heartbeat = setInterval(() => {
    res.write(': ping\n\n');
  }, 15000);
  req.on('close', () => {
    clearInterval(heartbeat);
    clients.delete(clientId);
  });
});

// Отправка события конкретному пользователю
function sendToUser(userId: string, event: string, data: object) {
  clients.forEach(({ res, userId: uid }) => {
    if (uid === userId) {
      res.write(`event: ${event}\n`);
      res.write(`data: ${JSON.stringify(data)}\n\n`);
    }
  });
}

// Broadcast всем
function broadcast(event: string, data: object) {
  clients.forEach(({ res }) => {
    res.write(`event: ${event}\n`);
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  });
}

לקוח המשתמש ב-EventSource

חיבור דרך EventSource עם תמיכה באירועים בעלי שם:

const eventSource = new EventSource('/api/events', { withCredentials: true });

// Дефолтные события (event: без имени)
eventSource.onmessage = (e) => {
    const data = JSON.parse(e.data);
    console.log('Message:', data);
};

// Именованные события
eventSource.addEventListener('order_update', (e) => {
    const order = JSON.parse(e.data);
    updateOrderStatus(order.orderId, order.status);
});

eventSource.addEventListener('notification', (e) => {
    showNotification(JSON.parse(e.data).message);
});

// Обработка ошибок
eventSource.onerror = (e) => {
    if (eventSource.readyState === EventSource.CLOSED) {
        console.log('Соединение закрыто, автоматически переподключается...');
    }
};

לפרטים נוספים על EventSource, עיין בתיעוד הרשמי: MDN Web Docs.

התקנה עם Redis Pub/Sub

sub.subscribe('user:events', (message) => {
  const { userId, event, data } = JSON.parse(message);
  sendToUser(userId, event, data);
});

// Из любого сервиса
await pub.publish('user:events', JSON.stringify({
  userId: 'user_123',
  event: 'payment_completed',
  data: { amount: 5000 }
}));

דוגמה לאירועים בעלי שם

אירוע פורמט נתונים
HTTP/1.1 200 OK Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive data: {"type":"notification","message":"Новый заказ"} event: order_update data: {"orderId":"123","status":"shipped"} id: msg_456 retry: 3000 : comment (игнорируется клиентом) app.get('/api/events', (req, res) => { const userId = req.user.id; res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive', 'X-Accel-Buffering': 'no', // для Nginx: отключить буферизацию }); // Немедленно отправляем первый пакет (обход nginx буферизации) res.write(':ok\n\n'); // Добавляем клиента в реестр const clientId = nanoid(); clients.set(clientId, { res, userId }); // Heartbeat каждые 15 секунд const heartbeat = setInterval(() => { res.write(': ping\n\n'); }, 15000); req.on('close', () => { clearInterval(heartbeat); clients.delete(clientId); }); }); // Отправка события конкретному пользователю function sendToUser(userId: string, event: string, data: object) { clients.forEach(({ res, userId: uid }) => { if (uid === userId) { res.write(`event: ${event}\n`); res.write(`data: ${JSON.stringify(data)}\n\n`); } }); } // Broadcast всем function broadcast(event: string, data: object) { clients.forEach(({ res }) => { res.write(`event: ${event}\n`); res.write(`data: ${JSON.stringify(data)}\n\n`); }); }
const eventSource = new EventSource('/api/events', { withCredentials: true }); // Дефолтные события (event: без имени) eventSource.onmessage = (e) => { const data = JSON.parse(e.data); console.log('Message:', data); }; // Именованные события eventSource.addEventListener('order_update', (e) => { const order = JSON.parse(e.data); updateOrderStatus(order.orderId, order.status); }); eventSource.addEventListener('notification', (e) => { showNotification(JSON.parse(e.data).message); }); // Обработка ошибок eventSource.onerror = (e) => { if (eventSource.readyState === EventSource.CLOSED) { console.log('Соединение закрыто, автоматически переподключается...'); } }; sub.subscribe('user:events', (message) => { const { userId, event, data } = JSON.parse(message); sendToUser(userId, event, data); }); // Из любого сервиса await pub.publish('user:events', JSON.stringify({ userId: 'user_123', event: 'payment_completed', data: { amount: 5000 } }));
notification {"type":"success","message":"..."}

מקרה אמיתי: SSE לפלטפורמה פיננסית

עבור סטארטאפ המספק נתוני מטבעות קריפטוגרפיים בזמן אמת, תכננו מערכת מבוססת SSE. הדרישה הייתה לשלוח שערים עבור יותר מ-50 זוגות מטבעות לאלפי משתמשים ללא עיכוב וללא העמסת מסדי נתונים של לקוחות. פתרון: כל משתמש נרשם לזוגות הרצויים דרך נקודת קצה מותאמת אישית, והשרתים מאגדים אירועים דרך Redis Pub/Sub. תוצאה: זמן השהיה באספקה מתחת ל-200 אלפיות השנייה גם עם 10,000 חיבורים. זה אפשר לנו לנטוש את WebSocket, לפשט את התשתית ולחסוך בפיתוח. אנו מבטיחים יציבות של תכנית זו בעומס גבוה.

איך להבטיח חיבור מחדש אמין?

SSE מתחבר מחדש אוטומטית, אבל חשוב להגדיר heartbeat. אם השרת לא שולח נתונים במשך 30 שניות, הדפדפן עלול לסגור את החיבור. לכן, אנו שולחים תגובה ריקה (order_update) כל 15 שניות. זה שומר על החיבור פעיל. בנוסף, השתמש בכותרת {"orderId":"123","status":"shipped"} עבור Nginx כדי למנוע חציצה של נתונים.

מגבלות SSE

  • שרת→לקוח בלבד — הלקוח לא יכול לשלוח נתונים דרך SSE (רק בקשות EventSource חדשות או קריאות API נפרדות).
  • מגבלת חיבורים ב-HTTP/1.1 — דפדפנים מגבילים ל-6 חיבורים לכל דומיין. SSE צורך אחד. פתרון: HTTP/2 (זרם מרובה יחיד).
  • אין תמיכה ב-IE — השתמש ב-polyfill payment_completed עבור דפדפנים ישנים.

מה כלול וטעויות נפוצות

  • עיצוב סכמת האירועים וסוגי הנתונים.
  • יישום נקודת הקצה בשרת עם אימות ו-heartbeat.
  • אינטגרציה בצד הלקוח (React hook, Angular service, או EventSource טבעי).
  • בדיקות עומס עד 10,000 חיבורים.
  • תיעוד הפרוטוקול והקוד.
  • הדרכה לצוות הלקוח.

טעויות נפוצות ביישום SSE:

  • שכחת חציצה של Nginx — חסר כותרת {"amount":5000,"currency":"USD"}.
  • שימוש ב-SSE לתקשורת דו-כיוונית — WebSocket מתאים יותר.
  • אי הגדרת heartbeat — החיבור עלול ליפול ולא להתאושש.

לוחות זמנים

נקודת קצה SSE עם אימות, heartbeat, והתקנת Redis: 3–5 ימים. עם אירועים מוגדרים, hook בצד הלקוח (useSSE), ואינטגרציית הודעות: 1–2 שבועות.

בואו נבחן את הפרויקט שלכם — צרו קשר לייעוץ. במהלך השנים, יישמנו פתרונות רבים מבוססי SSE, ומבטיחים יציבות בעומס גבוה. אם אתם צריכים ליישם עדכונים חיים, קבלו ייעוץ היום.