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

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

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

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

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

  • लंबे समय तक चलने वाली पोलिंग (Hanging GET / COMET): अगर सर्वर पर डेटा उपलब्ध नहीं है, तो सर्वर तब तक अनुरोध को खुला रखता है, जब तक नया डेटा उपलब्ध नहीं हो जाता. इसलिए, इस तकनीक को अक्सर "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 Content-Type के साथ भेजा जाता है, जो SSE फ़ॉर्मैट का पालन करता है. बुनियादी तौर पर, रिस्पॉन्स में data: लाइन के बाद आपका मैसेज होना चाहिए. इसके बाद, स्ट्रीम को खत्म करने के लिए दो "\n" वर्ण होने चाहिए:

data: My message\n\n

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

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

हर लाइन के आखिर में एक "\n" होना चाहिए. आखिरी लाइन के आखिर में दो "\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()));
?>

यहां 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 या 200 OK के बजाय कोई दूसरा एचटीटीपी स्टेटस दें (जैसे, 404 Not Found).

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

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

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