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

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

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

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

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

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

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

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

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

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

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

على الرغم من ذلك، تحتاج أحيانًا إلى اتصال أحادي الاتجاه من الخادم. على سبيل المثال، عندما يقوم أحد الأصدقاء بتحديث حالته أو مؤشرات الأسهم أو خلاصات الأخبار أو الآليات الآلية الأخرى لإرسال البيانات أو بعبارةٍ أخرى، تحديث لقاعدة بيانات 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.

تنسيق مجموعة بث الحدث

ترتبط مسألة إرسال ساحة مشاركات أحداث من المصدر بإنشاء رد بنص عادي، يُعرض من خلال نوع محتوى 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('') لإعادة إنشاء الرسالة sans "\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:، يليها اسم فريد للحدث، فسيتم ربط الحدث بهذا الاسم. في جهاز العميل، يمكن إعداد أداة معالجة الحدث للاستماع إلى هذا الحدث المحدّد.

فعلى سبيل المثال، يرسل ناتج الخادم التالي ثلاثة أنواع من الأحداث، "رسالة" عامة الحدث و"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

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

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 باستخدام معالِج سريع:

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