सर्वर के भेजे गए इवेंट के साथ अपडेट स्ट्रीम करें

सर्वर से भेजे गए इवेंट (एसएसई), एचटीटीपी कनेक्शन वाले सर्वर से क्लाइंट को अपने-आप अपडेट भेजते हैं. कनेक्शन बन जाने के बाद, सर्वर डेटा ट्रांसमिशन शुरू कर सकते हैं.

अपने वेब ऐप्लिकेशन से पुश नोटिफ़िकेशन भेजने के लिए, हो सकता है कि आप SSE का इस्तेमाल करना चाहें. SSEs एक दिशा में जानकारी भेजते हैं, इसलिए आपको क्लाइंट से अपडेट नहीं मिलेंगे.

एसएसई का सिद्धांत आपको जाना-पहचाना हो सकता है. वेब ऐप्लिकेशन, सर्वर से जनरेट होने वाले अपडेट की सदस्यता ले लेता है और जब भी कोई नया इवेंट होता है, तो क्लाइंट को इसकी सूचना भेजी जाती है. सर्वर से भेजे गए इवेंट को असल में समझने के लिए, हमें AJAX के पहले से मौजूद इसकी सीमाओं को समझना होगा. ऐसे कॉन्टेंट के कुछ उदाहरण यहां दिए गए हैं:

  • पोलिंग: ऐप्लिकेशन, डेटा के लिए सर्वर को बार-बार पोल करता है. इस तकनीक का इस्तेमाल ज़्यादातर AJAX ऐप्लिकेशन में किया जाता है. एचटीटीपी प्रोटोकॉल की मदद से, डेटा फ़ेच करने की प्रोसेस, अनुरोध और रिस्पॉन्स फ़ॉर्मैट के हिसाब से होती है. क्लाइंट एक अनुरोध करता है और इंतज़ार करता है कि सर्वर डेटा के साथ जवाब दे. अगर कोई जवाब उपलब्ध नहीं है, तो कोई जवाब नहीं मिलता है. अतिरिक्त पोल की वजह से एचटीटीपी ओवरहेड बढ़ जाता है.

  • लॉन्ग पोल (Hanging GET / COMET): अगर सर्वर के पास डेटा उपलब्ध नहीं है, तो नया डेटा उपलब्ध होने तक सर्वर अनुरोध को स्वीकार करके रखता है. इसलिए, इस तकनीक को अक्सर "हैंगिंग जीईटी" भी कहा जाता है. जानकारी उपलब्ध होने पर, सर्वर जवाब देता है, कनेक्शन बंद कर देता है, और प्रोसेस दोहराई जाती है. इसलिए, सर्वर लगातार नए डेटा के साथ रिस्पॉन्स दे रहा है. इसे सेट अप करने के लिए, डेवलपर आम तौर पर 'इनफ़ाइनाइट' iframe में स्क्रिप्ट टैग जोड़ने जैसे हैक का इस्तेमाल करते हैं.

सर्वर से भेजे गए इवेंट को शुरू से ही इस तरह डिज़ाइन किया गया है कि वे बेहतर तरीके से काम कर सकें. एसएसई से संपर्क करते समय, सर्वर आपके ऐप्लिकेशन में जब चाहे डेटा भेज सकता है. इसके लिए, शुरुआती अनुरोध करने की ज़रूरत नहीं होती. दूसरे शब्दों में, अपडेट होते ही उन्हें सर्वर से क्लाइंट पर स्ट्रीम किया जा सकता है. एसएसई, सर्वर और क्लाइंट के बीच एक हीतरफ़ा चैनल खोलते हैं.

सर्वर के भेजे गए इवेंट और लंबी पोलिंग के बीच मुख्य अंतर यह है कि एसएसई को सीधे ब्राउज़र मैनेज करता है और उपयोगकर्ता को सिर्फ़ मैसेज सुनने होते हैं.

सर्वर से भेजे गए इवेंट बनाम WebSockets

WebSockets पर, सर्वर से भेजे गए इवेंट को क्यों चुना जाएगा? यह अच्छा सवाल है.

WebSockets में दो-तरफ़ा और फ़ुल-डूप्लेक्स कम्यूनिकेशन वाला एक बेहतरीन प्रोटोकॉल है. दो-तरफ़ा चैनल, गेम, मैसेजिंग ऐप्लिकेशन, और इस्तेमाल के किसी भी ऐसे मामले के लिए बेहतर है जहां आपको दोनों तरफ़ से करीब-करीब रीयल-टाइम अपडेट की ज़रूरत हो.

हालांकि, कभी-कभी आपको सर्वर से सिर्फ़ एकतरफ़ा कम्यूनिकेशन की ज़रूरत होती है. उदाहरण के लिए, जब कोई दोस्त अपनी स्थिति, स्टॉक टिकर, न्यूज़ फ़ीड या अपने-आप काम करने वाले दूसरे डेटा पुश तरीकों को अपडेट करता है. दूसरे शब्दों में, क्लाइंट-साइड वेब एसक्यूएल डेटाबेस या IndexedDB ऑब्जेक्ट स्टोर के लिए अपडेट. अगर आपको किसी सर्वर पर डेटा भेजना है, तो XMLHttpRequest हमेशा आपके साथ काम करेगा.

एसएसई, एचटीटीपी पर भेजे जाते हैं. काम करने के लिए कोई खास प्रोटोकॉल या सर्वर लागू नहीं है. प्रोटोकॉल को हैंडल करने के लिए WebSocket को फ़ुल-डूप्लेक्स कनेक्शन और नए WebSocket सर्वर की ज़रूरत होगी.

इसके अलावा, सर्वर के भेजे गए इवेंट में कई ऐसी सुविधाएं होती हैं जो WebSockets डिज़ाइन के हिसाब से नहीं हैं. इनमें अपने-आप फिर से कनेक्ट होना, इवेंट आईडी, और आर्बिट्ररी इवेंट भेजने की सुविधा शामिल है.

JavaScript की मदद से कोई EventSource बनाना

किसी इवेंट स्ट्रीम की सदस्यता लेने के लिए, EventSource ऑब्जेक्ट बनाएं और उसे अपनी स्ट्रीम का यूआरएल दें:

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 प्रॉपर्टी में उपलब्ध हो जाता है. मज़ेदार बात यह है कि कनेक्शन बंद होने पर, ब्राउज़र करीब तीन सेकंड के बाद, सोर्स से अपने-आप कनेक्ट हो जाता है. यहां तक कि आपके सर्वर को लागू करने से, फिर से कनेक्ट होने के इस टाइम आउट पर कंट्रोल भी हो सकता है.

हो गया. आपका क्लाइंट अब stream.php के इवेंट प्रोसेस कर सकता है.

इवेंट स्ट्रीम का फ़ॉर्मैट

सोर्स से इवेंट स्ट्रीम भेजना, एक सादा टेक्स्ट जवाब बनाने के लिए है. इसे text/event-stream कॉन्टेंट-टाइप के साथ दिखाया जाता है, जो एसएसई फ़ॉर्मैट के मुताबिक होता है. अपने मूल रूप में, जवाब में एक data: लाइन, उसके बाद आपका मैसेज, और स्ट्रीम को खत्म करने के लिए दो "\n" वर्ण होने चाहिए:

data: My message\n\n

मल्टी-लाइन डेटा

अगर आपका मैसेज लंबा है, तो उसे अलग-अलग दिखाने के लिए, एक से ज़्यादा data: लाइनें इस्तेमाल करें. data: से शुरू होने वाली दो या उससे ज़्यादा लगातार लाइनों को डेटा का एक हिस्सा माना जाता है. इसका मतलब है कि सिर्फ़ एक message इवेंट चालू किया गया है.

हर लाइन एक "\n" पर खत्म होनी चाहिए (आखिरी लाइन को छोड़कर, जिसके आखिर में दो होना चाहिए). आपके message हैंडलर को भेजा गया नतीजा एक सिंगल स्ट्रिंग होती है, जिसे न्यूलाइन वर्णों के आधार पर इकट्ठा किया जाता है. उदाहरण के लिए:

data: first line\n
data: second line\n\n</pre>

इससे e.data में "पहली लाइन\nदूसरी लाइन" जनरेट होती है. इसके बाद, "\n" मैसेज के वर्णों को फिर से बनाने के लिए, e.data.split('\n').join('') का इस्तेमाल किया जा सकता है.

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

आईडी सेट करने से ब्राउज़र, ट्रिगर किए गए आखिरी इवेंट को ट्रैक कर पाता है. इससे, अगर सर्वर से कनेक्शन टूट जाता है, तो नए अनुरोध के साथ एक खास एचटीटीपी हेडर (Last-Event-ID) सेट हो जाता है. इससे ब्राउज़र यह तय कर पाता है कि कौनसा इवेंट फ़ायर करने के लिए सही है. message इवेंट में, e.lastEventId प्रॉपर्टी शामिल है.

फिर से कनेक्ट करने के टाइम आउट को कंट्रोल करें

हर कनेक्शन बंद होने के करीब तीन सेकंड बाद, ब्राउज़र सोर्स से दोबारा कनेक्ट करने की कोशिश करता है. उस टाइम आउट को बदलने के लिए, retry: से शुरू होने वाली लाइन शामिल करें. इसके बाद, फिर से कनेक्ट करने की कोशिश करने से पहले, इंतज़ार करने के लिए मिलीसेकंड की संख्या डालें.

यहां दिए गए उदाहरण में, 10 सेकंड के बाद फिर से कनेक्ट करने की कोशिश की गई है:

retry: 10000\n
data: hello world\n\n

इवेंट का नाम तय करें

कोई एक इवेंट सोर्स, इवेंट का नाम शामिल करके अलग-अलग तरह के इवेंट जनरेट कर सकता है. अगर event: से शुरू होने वाली कोई लाइन मौजूद है और इसके बाद इवेंट का कोई यूनीक नाम है, तो इवेंट उस नाम से जुड़ा होगा. क्लाइंट पर, किसी खास इवेंट को सुनने के लिए इवेंट लिसनर को सेटअप किया जा सकता है.

उदाहरण के लिए, नीचे दिया गया सर्वर आउटपुट तीन तरह के इवेंट, सामान्य 'मैसेज' इवेंट, 'userlogon', और 'अपडेट' इवेंट भेजता है:

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

यहां एक्सप्रेशन हैंडलर का इस्तेमाल करके, नोड 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 का इस्तेमाल न करें या 200 OK के अलावा, कोई एचटीटीपी स्टेटस दिखाएं. जैसे, 404 Not Found.

दोनों तरीकों से ब्राउज़र को फिर से कनेक्ट करने से रोका जाता है.

सुरक्षा के बारे में जानकारी

EventSource से जनरेट किए गए अनुरोधों पर वही ऑरिजिन नीतियां लागू होती हैं जो फ़ेच करने जैसे अन्य नेटवर्क एपीआई पर लागू होती हैं. अगर आपको अपने सर्वर पर SSE एंडपॉइंट को अलग-अलग ऑरिजिन से ऐक्सेस करना है, तो क्रॉस ऑरिजिन रिसॉर्स शेयरिंग (सीओआरएस) से चालू करने का तरीका जानें.