بث التحديثات باستخدام الأحداث التي أرسلها الخادم

ترسل الأحداث التي يرسلها الخادم (SSE) تحديثات تلقائية إلى عميل من خادم باستخدام اتصال HTTP. بمجرد إنشاء الاتصال، يمكن للخوادم بدء نقل البيانات.

يمكنك استخدام تكنولوجيا SSE لإرسال الإشعارات الفورية من تطبيق الويب، وترسل هذه الجلسات المعلومات في اتجاه واحد، وبالتالي لن تتلقّى أي تحديثات من العميل.

قد يكون مفهوم جنوبية شرقية مألوفًا. ويشترك تطبيق الويب في تدفق التحديثات التي يُنشئها الخادم، ومتى وقع حدث جديد، يتم إرسال إشعار إلى العميل. ولكن لفهم الأحداث التي يرسلها الخادم حقًا، نحتاج إلى فهم القيود المفروضة على سابقات AJAX. يشمل ذلك ما يلي:

  • استطلاعات الرأي: يتحقّق التطبيق من نتائج البحث عن الخادم بشكل متكرر. تستخدم غالبية تطبيقات AJAX هذه التقنية. باستخدام بروتوكول HTTP، يدور استرجاع البيانات حول تنسيق الطلب والاستجابة. يقدم العميل طلبًا وينتظر حتى يستجيب الخادم بالبيانات. وإذا لم يكن أي منها متاحًا، سيتم عرض رد فارغ. تؤدي استطلاعات الرأي الإضافية إلى إنشاء قدر أكبر من عبء HTTP.

  • الاستطلاع الطويل (تعليق GET / COMET): في حال عدم توفر بيانات لدى الخادم، سيحتفظ الخادم بالطلب مفتوحًا إلى أن تتم إتاحة بيانات جديدة. وبالتالي، يُشار إلى هذه التقنية غالبًا باسم "الحصول على تعليق معلّق". وعند توفر المعلومات، يستجيب الخادم ويغلق الاتصال ويتكرر العملية. وبالتالي، يستجيب الخادم باستمرار بالبيانات الجديدة. لإعداد هذا الخيار، يستخدم المطوّرون عادةً أساليب مخترَقة مثل إلحاق علامات نصوص برمجية بإطار iframe "غير محدود".

تم تصميم الأحداث التي يرسلها الخادم من الألف إلى الياء لتحقيق الكفاءة. عند التواصل مع مجالات SSE، يمكن للخادم إرسال البيانات إلى تطبيقك متى أراد ذلك، وبدون الحاجة إلى تقديم طلب أولي. بمعنى آخر، يمكن بث التحديثات من خادم إلى آخر عند حدوثها. تفتح SSE قناة واحدة أحادية الاتجاه بين الخادم والعميل.

يكمن الاختلاف الرئيسي بين الأحداث التي يرسلها الخادم والتجميع الطويل في أنه يتم التعامل مع مشكلات SSE في المجال بأكمله مباشرةً من خلال المتصفح ولا يكون على المستخدم سوى الاستماع إلى الرسائل.

الأحداث التي يرسلها الخادم مقابل WebSockets

لماذا تختارون الأحداث التي يرسلها الخادم بدلاً من WebSockets؟ سؤال جيد.

تحتوي WebSockets على بروتوكول غني مع اتصال ثنائي الاتجاه مزدوج الاتجاه. القناة الثنائية هي أفضل للألعاب وتطبيقات المراسلة وأي حالة استخدام تحتاج فيها إلى تحديثات في الوقت الفعلي تقريبًا في كلا الاتجاهين.

ومع ذلك، قد تحتاج أحيانًا إلى اتصال أحادي الاتجاه من أحد الخوادم. على سبيل المثال، عندما يعدّل صديق حالته أو مؤشرات الأسهم أو خلاصات الأخبار أو غيرها من الآليات الآلية لإرسال البيانات. بمعنى آخر، تحديث لقاعدة بيانات ويب SQL (لغة الاستعلام البنيوية) من جهة العميل أو مخزن عناصر IndexedDB. إذا كنت بحاجة إلى إرسال بيانات إلى أي خادم، فسيكون XMLHttpRequest صديقًا دائمًا.

يتم إرسال محركات البحث الآمن عبر HTTP. لا يوجد بروتوكول خاص أو تنفيذ خاص للخادم لبدء العمل. تتطلب تقنية WebSocket اتصالات مزدوجة الاتجاه وخوادم WebSocket جديدة للتعامل مع البروتوكول.

علاوة على ذلك، تحتوي الأحداث التي يرسلها الخادم على مجموعة متنوعة من الميزات التي يفتقر إليها تصميم WebSocket، بما في ذلك إعادة الربط التلقائي ومعرّفات الأحداث والقدرة على إرسال أحداث عشوائية.

إنشاء 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.

تنسيق بث الحدث

إنّ إرسال بث حدث من المصدر يتعلق بإنشاء استجابة نص عادي، يتم عرضها مع نوع محتوى 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:، متبوعًا باسم فريد للحدث، سيتم ربط الحدث بهذا الاسم. في البرنامج، يمكن إعداد أداة معالجة الحدث للاستماع إلى هذا الحدث بعينه.

على سبيل المثال، ترسل ناتج الخادم التالي ثلاثة أنواع من الأحداث، حدث "رسالة" عام وحدث "تسجيل دخول مستخدم" وحدث "تحديث":

data: {"msg": "First message"}\n\n
event: userlogon\n
data: {"username": "John123"}\n\n
event: update\n
data: {"username": "John123", "emotion": "happy"}\n\n

من خلال إعداد أدوات معالجة الأحداث في البرنامج:

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();

لإلغاء البث من الخادم، يمكنك الاستجابة باستخدام Content-Type غير text/event-stream أو عرض حالة HTTP بخلاف 200 OK (مثل 404 Not Found).

وتمنع كلتا الطريقتين المتصفح من إعادة إنشاء الاتصال.

لمحة عن الأمان

وتخضع الطلبات التي يتمّ إنشاؤها من خلال EventSource لسياسات المصدر نفسه المطبَّقة على واجهات برمجة تطبيقات الشبكة الأخرى، مثل الجلب. إذا كنت بحاجة إلى إتاحة الوصول إلى نقطة نهاية SSE على الخادم من مصادر مختلفة، يُرجى الاطّلاع على كيفية تفعيلها باستخدام مشاركة الموارد المتعدّدة المصادر (CORS).