אירועים שנשלחים מהשרת (SSE) שולחים עדכונים אוטומטיים ללקוח מהשרת, באמצעות חיבור HTTP. אחרי שהחיבור ייווצר, השרתים יוכלו להתחיל להעביר נתונים.
כדאי להשתמש ב-SSE כדי לשלוח התראות דחיפה מאפליקציית האינטרנט. אירועי SSE שולחים מידע בכיוון אחד, ולכן לא תקבלו עדכונים מהלקוח.
יכול להיות שהמושג 'שירותי SSE' מוכר לכם. אפליקציית אינטרנט 'נרשמת' לעדכונים ששרת יוצר, וכשהאירוע החדש מתרחש, נשלחת התראה ללקוח. אבל כדי להבין באמת את האירועים שנשלחים מהשרת, צריך להבין את המגבלות של קודמי ה-AJAX. בין היתר, אסור:
סקרים: האפליקציה מבצעת סקרים חוזרים ונשנים בשרת כדי לקבל נתונים. רוב האפליקציות של AJAX משתמשות בשיטה הזו. בפרוטוקול HTTP, אחזור הנתונים מתבסס על פורמט של בקשה ותגובה. הלקוח שולח בקשה וממתין לתגובה מהשרת עם נתונים. אם אף אחת מהן לא זמינה, תוחזר תשובה ריקה. סקרים נוספים יוצרים תקורה גבוהה יותר ב-HTTP.
Long polling (Hanging GET / COMET): אם אין נתונים זמינים בשרת, הבקשה נשארת פתוחה עד שיהיו נתונים חדשים. לכן, השיטה הזו נקראת לעיתים קרובות 'Hanging GET'. כשהמידע זמין, השרת מגיב, סוגר את החיבור והתהליך חוזר על עצמו. לכן, השרת מגיב כל הזמן עם נתונים חדשים. כדי להגדיר את זה, המפתחים בדרך כלל משתמשים בהאקים כמו הוספת תגי סקריפט ל-iframe 'ללא הגבלה'.
אירועים שנשלחים מהשרת תוכננו מראש כדי להיות יעילים. כשמתקשרים עם SSE, השרת יכול לדחוף נתונים לאפליקציה שלכם מתי שהוא רוצה, בלי צורך לשלוח בקשה ראשונית. במילים אחרות, אפשר להעביר עדכונים בשידור חי מהשרת ללקוח בזמן שהם מתרחשים. שרתי SSE פותחים ערוץ יחיד בכיוון אחד בין השרת ללקוח.
ההבדל העיקרי בין אירועים שנשלחים מהשרת לבין פוליגרפיה ארוכה הוא שאירועי SSE מטופלים ישירות על ידי הדפדפן, והמשתמש צריך רק להאזין להודעות.
אירועים שנשלחים מהשרת לעומת WebSockets
למה כדאי לבחור באירועים שנשלחים מהשרת במקום ב-WebSockets? שאלה טובה.
ל-WebSockets יש פרוטוקול עשיר עם תקשורת דו-כיוונית ברוחב פס מלא. ערוץ דו-כיווני מתאים יותר למשחקים, לאפליקציות שליחת הודעות לכל תרחיש לדוגמה שבו צריך עדכונים כמעט בזמן אמת בשני הכיוונים.
עם זאת, לפעמים צריך רק תקשורת חד-כיוונית מהשרת.
לדוגמה, כשחבר מעדכן את הסטטוס שלו, נתוני מניות, פידים של חדשות או מנגנונים אחרים להעברת נתונים באופן אוטומטי. במילים אחרות, עדכון של מסד נתוני Web SQL או של מאגר אובייקטים של IndexedDB בצד הלקוח.
אם אתם צריכים לשלוח נתונים לשרת, XMLHttpRequest
תמיד יעזור לכם.
הודעות SSE נשלחות באמצעות HTTP. אין צורך בהטמעה מיוחדת של שרת או פרוטוקול כדי להפעיל את השירות. כדי לטפל בפרוטוקול, WebSockets דורשים חיבורים דו-כיווניים מלאים ושרתי WebSocket חדשים.
בנוסף, לאירועים שנשלחים מהשרת יש מגוון תכונות שחסרות ל-WebSockets מעצם הגדרתם, כולל חיבור מחדש אוטומטי, מזהי אירועים ויכולת לשלוח אירועים שרירותיים.
יצירת EventSource באמצעות JavaScript
כדי להירשם למינוי לזרם אירועים, יוצרים אובייקט EventSource
ומעבירים לו את כתובת ה-URL של הזרם:
const source = new EventSource('stream.php');
בשלב הבא מגדירים טיפול באירוע message
. אפשר גם להאזין ל-open
ול-error
:
source.addEventListener('message', (e) => {
console.log(e.data);
});
source.addEventListener('open', (e) => {
// Connection was opened.
});
source.addEventListener('error', (e) => {
if (e.readyState == EventSource.CLOSED) {
// Connection was closed.
}
});
כשמתבצעת דחיפה של עדכונים מהשרת, הטיפול של onmessage
מופעל והנתונים החדשים יהיו זמינים בנכס e.data
שלו. החלק הקסום הוא שבכל פעם שהחיבור נסגר, הדפדפן מתחבר מחדש למקור באופן אוטומטי אחרי כ-3 שניות. להטמעה שלכם בשרת יכולה להיות אפילו שליטה על הזמן הקצוב לחיבור מחדש.
זה הכול. עכשיו הלקוח יכול לעבד אירועים מ-stream.php
.
פורמט של מקור נתונים של אירועים
כדי לשלוח מקור של אירועים, צריך ליצור תשובה בטקסט ללא עיצוב, עם Content-Type text/event-stream
, שתועבר בפורמט SSE.
בצורתה הבסיסית, התשובה צריכה להכיל שורה data:
, ואחריה את ההודעה שלכם, ואחריה שני תווים מסוג \n כדי לסיים את הסטרימינג:
data: My message\n\n
נתונים בכמה שורות
אם ההודעה ארוכה יותר, אפשר לפצל אותה באמצעות כמה שורות data:
.
שתי שורות רצופות או יותר שמתחילות ב-data:
נחשבות כחלק יחיד של נתונים, כלומר מתבצע ירי של אירוע message
אחד בלבד.
כל שורה צריכה להסתיים בתו 'n' יחיד (למעט השורה האחרונה, שצריכה להסתיים בשני תווים כאלה). התוצאה שמועברת למטפל message
היא מחרוזת אחת שמקושרת באמצעות תווים של שורת חדשה. לדוגמה:
data: first line\n
data: second line\n\n</pre>
הפונקציה הזו יוצרת את הטקסט 'שורה ראשונה\nשורה שנייה' ב-e.data
. לאחר מכן אפשר להשתמש ב-e.data.split('\n').join('')
כדי לשחזר את ההודעה ללא התווים \n.
שליחת נתוני JSON
שימוש בכמה שורות עוזר לשלוח JSON בלי לשבור את התחביר:
data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n
וקוד אפשרי בצד הלקוח לטיפול במקור הנתונים הזה:
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.id, data.msg);
});
שיוך מזהה לאירוע
כדי לשלוח מזהה ייחודי עם אירוע של מקור נתונים, צריך לכלול שורה שמתחילה ב-id:
:
id: 12345\n
data: GOOG\n
data: 556\n\n
הגדרת מזהה מאפשרת לדפדפן לעקוב אחרי האירוע האחרון שהופעל, כך שאם החיבור לשרת יתנתק, תוגדר כותרת HTTP מיוחדת (Last-Event-ID
) עם הבקשה החדשה. כך הדפדפן יכול לקבוע איזה אירוע מתאים להפעלה.
האירוע message
מכיל מאפיין e.lastEventId
.
שליטה במכסת הזמן להתחברות מחדש
הדפדפן מנסה להתחבר מחדש למקור כ-3 שניות אחרי שכל חיבור נסגר. כדי לשנות את הזמן הקצוב לתפוגה, מוסיפים שורה שמתחילה ב-retry:
ואחריה מספר אלפיות השנייה שצריך להמתין לפני שמנסים להתחבר מחדש.
בדוגמה הבאה מתבצע ניסיון התחברות מחדש אחרי 10 שניות:
retry: 10000\n
data: hello world\n\n
נותנים שם לאירוע.
מקור אירועים יחיד יכול ליצור אירועים מסוגים שונים על ידי הכללת שם אירוע. אם יש שורה שמתחילה ב-event:
ואחריה שם ייחודי לאירוע, האירוע משויך לשם הזה.
אפשר להגדיר בלקוח פונקציית event listener כדי להאזין לאירוע הספציפי הזה.
לדוגמה, פלט השרת הבא שולח שלושה סוגים של אירועים: אירוע 'message' כללי, אירוע 'userlogon' ואירוע 'update':
data: {"msg": "First message"}\n\n
event: userlogon\n
data: {"username": "John123"}\n\n
event: update\n
data: {"username": "John123", "emotion": "happy"}\n\n
כשפונקציות event listener מוגדרות בצד הלקוח:
source.addEventListener('message', (e) => {
const data = JSON.parse(e.data);
console.log(data.msg);
});
source.addEventListener('userlogon', (e) => {
const data = JSON.parse(e.data);
console.log(`User login: ${data.username}`);
});
source.addEventListener('update', (e) => {
const data = JSON.parse(e.data);
console.log(`${data.username} is now ${data.emotion}`);
};
דוגמאות לשרתים
זוהי הטמעה בסיסית של שרת ב-PHP:
<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.
/**
* Constructs the SSE data format and flushes that data to the client.
*
* @param string $id Timestamp/id of this connection.
* @param string $msg Line of text that should be transmitted.
**/
function sendMsg($id, $msg) {
echo "id: $id" . PHP_EOL;
echo "data: $msg" . PHP_EOL;
echo PHP_EOL;
ob_flush();
flush();
}
$serverTime = time();
sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
?>
הנה הטמעה דומה ב-Node JS באמצעות טיפול של Express:
app.get('/events', (req, res) => {
// Send the SSE header.
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
// Sends an event to the client where the data is the current date,
// then schedules the event to happen again after 5 seconds.
const sendEvent = () => {
const data = (new Date()).toLocaleTimeString();
res.write("data: " + data + '\n\n');
setTimeout(sendEvent, 5000);
};
// Send the initial event immediately.
sendEvent();
});
sse-node.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<script>
const source = new EventSource('/events');
source.onmessage = (e) => {
const content = document.createElement('div');
content.textContent = e.data;
document.body.append(content);
};
</script>
</body>
</html>
ביטול של שידור אירועים
בדרך כלל, הדפדפן מתחבר מחדש באופן אוטומטי למקור האירועים כשהחיבור נסגר, אבל אפשר לבטל את ההתנהגות הזו מהלקוח או מהשרת.
כדי לבטל את הסטרימינג מהלקוח, צריך לבצע את הקריאה הבאה:
source.close();
כדי לבטל את הסטרימינג מהשרת, צריך להשיב עם text/event-stream
שאינו Content-Type
או להחזיר סטטוס HTTP שאינו 200 OK
(למשל 404 Not Found
).
שתי השיטות מונעות מהדפדפן ליצור מחדש את החיבור.
כמה מילים על אבטחה
בקשות שנוצרות על ידי EventSource כפופות למדיניות המקור הזהה כמו API אחרים של רשתות, כמו fetch. אם אתם צריכים שאפשר יהיה לגשת לנקודת הקצה של SSE בשרת ממקורות שונים, תוכלו לקרוא איך מפעילים את זה באמצעות שיתוף משאבים בין מקורות (CORS).