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

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

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

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

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

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

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

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

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

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

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

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

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

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

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 में "first line\nsecond line" दिखता है. इसके बाद, मैसेज "\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', और '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()));
?>

एक्सप्रेस हैंडलर का इस्तेमाल करके, नोड जेएस पर इसे लागू करने का तरीका यहां दिया गया है:

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 से जनरेट किए गए अनुरोधों पर, एक ही ऑरिजिन से जुड़ी वही नीतियां लागू होती हैं जो फ़ेच जैसे अन्य नेटवर्क एपीआई पर लागू होती हैं. अगर आपको अपने सर्वर पर एसएसई एंडपॉइंट को अलग-अलग ऑरिजिन से ऐक्सेस करना है, तो क्रॉस-ऑरिजिन रिसॉर्स शेयरिंग (सीओआरएस) की मदद से इसे चालू करने का तरीका पढ़ें.