সার্ভার-প্রেরিত ইভেন্টগুলির সাথে স্ট্রীম আপডেট

সার্ভার-প্রেরিত ইভেন্ট (SSEs) একটি HTTP সংযোগ সহ একটি সার্ভার থেকে একটি ক্লায়েন্টকে স্বয়ংক্রিয় আপডেট পাঠায়। সংযোগ স্থাপন হয়ে গেলে, সার্ভারগুলি ডেটা ট্রান্সমিশন শুরু করতে পারে।

আপনি আপনার ওয়েব অ্যাপ থেকে পুশ বিজ্ঞপ্তি পাঠাতে SSEs ব্যবহার করতে চাইতে পারেন। SSE গুলি এক দিকে তথ্য পাঠায়, এইভাবে আপনি ক্লায়েন্টের কাছ থেকে আপডেট পাবেন না।

SSEs ধারণা পরিচিত হতে পারে. একটি ওয়েব অ্যাপ সার্ভার দ্বারা উত্পন্ন আপডেটের একটি স্ট্রীমে "সাবস্ক্রাইব করে" এবং যখনই একটি নতুন ইভেন্ট ঘটে, ক্লায়েন্টকে একটি বিজ্ঞপ্তি পাঠানো হয়। কিন্তু প্রকৃতপক্ষে সার্ভার-প্রেরিত ইভেন্টগুলি বুঝতে, আমাদের এর AJAX পূর্বসূরীদের সীমাবদ্ধতাগুলি বুঝতে হবে। এর মধ্যে রয়েছে:

  • পোলিং : অ্যাপ্লিকেশনটি বারবার ডেটার জন্য একটি সার্ভার পোল করে। এই কৌশলটি বেশিরভাগ AJAX অ্যাপ্লিকেশন দ্বারা ব্যবহৃত হয়। এইচটিটিপি প্রোটোকলের সাথে, ডেটা আনার প্রক্রিয়াটি একটি অনুরোধ এবং প্রতিক্রিয়া বিন্যাসের চারপাশে ঘোরে। ক্লায়েন্ট একটি অনুরোধ করে এবং সার্ভারের ডেটা সহ সাড়া দেওয়ার জন্য অপেক্ষা করে। যদি কোনটি উপলব্ধ না হয়, একটি খালি প্রতিক্রিয়া ফেরত দেওয়া হয়। অতিরিক্ত পোলিং বৃহত্তর HTTP ওভারহেড তৈরি করে।

  • দীর্ঘ পোলিং (হ্যাংিং গেইটি / COMET) : সার্ভারে ডেটা উপলব্ধ না থাকলে, নতুন ডেটা উপলব্ধ না হওয়া পর্যন্ত সার্ভার অনুরোধটি খোলা রাখে। তাই, এই কৌশলটিকে প্রায়ই "হ্যাংিং গেইটি" হিসাবে উল্লেখ করা হয়। যখন তথ্য উপলব্ধ হয়, সার্ভার প্রতিক্রিয়া জানায়, সংযোগ বন্ধ করে এবং প্রক্রিয়াটি পুনরাবৃত্তি হয়। এইভাবে, সার্ভার ক্রমাগত নতুন তথ্যের সাথে সাড়া দিচ্ছে। এটি সেট আপ করতে, বিকাশকারীরা সাধারণত হ্যাক ব্যবহার করে যেমন একটি 'অসীম' আইফ্রেমে স্ক্রিপ্ট ট্যাগ যুক্ত করা।

সার্ভার-প্রেরিত ইভেন্ট, দক্ষ হতে স্থল থেকে ডিজাইন করা হয়েছে. SSE-এর সাথে যোগাযোগ করার সময়, একটি সার্ভার যখনই চায় আপনার অ্যাপে ডেটা পুশ করতে পারে, একটি প্রাথমিক অনুরোধ করার প্রয়োজন ছাড়াই। অন্য কথায়, আপডেটগুলি সার্ভার থেকে ক্লায়েন্টে স্ট্রিম করা যেতে পারে যেমনটি ঘটে। এসএসই সার্ভার এবং ক্লায়েন্টের মধ্যে একটি একক অভিমুখী চ্যানেল খোলে।

সার্ভার-প্রেরিত ইভেন্ট এবং দীর্ঘ-ভোটের মধ্যে প্রধান পার্থক্য হল যে SSEগুলি সরাসরি ব্রাউজার দ্বারা পরিচালিত হয় এবং ব্যবহারকারীকে কেবল বার্তা শুনতে হয়।

সার্ভার-প্রেরিত ইভেন্ট বনাম WebSockets

কেন আপনি WebSockets এর মাধ্যমে সার্ভার-প্রেরিত ইভেন্টগুলি বেছে নেবেন? ভাল প্রশ্ন.

WebSockets এর দ্বি-দিকনির্দেশক, পূর্ণ-দ্বৈত যোগাযোগ সহ একটি সমৃদ্ধ প্রোটোকল রয়েছে। একটি দ্বি-মুখী চ্যানেল গেম, মেসেজিং অ্যাপ এবং যেকোনো ব্যবহারের ক্ষেত্রে ভালো যেখানে আপনার উভয় দিকের রিয়েল-টাইম আপডেটের প্রয়োজন।

যাইহোক, কখনও কখনও আপনার শুধুমাত্র একটি সার্ভার থেকে একমুখী যোগাযোগের প্রয়োজন হয়। উদাহরণস্বরূপ, যখন একজন বন্ধু তাদের স্ট্যাটাস, স্টক টিকার্স, নিউজ ফিড বা অন্যান্য স্বয়ংক্রিয় ডেটা পুশ মেকানিজম আপডেট করে। অন্য কথায়, একটি ক্লায়েন্ট-সাইড ওয়েব SQL ডেটাবেস বা IndexedDB অবজেক্ট স্টোরের একটি আপডেট। আপনার যদি কোনো সার্ভারে ডেটা পাঠাতে হয়, XMLHttpRequest সর্বদা বন্ধু।

SSE গুলি HTTP এর মাধ্যমে পাঠানো হয়। কাজ করার জন্য কোন বিশেষ প্রোটোকল বা সার্ভার বাস্তবায়ন নেই। প্রোটোকল পরিচালনা করার জন্য ওয়েবসকেটগুলির সম্পূর্ণ-ডুপ্লেক্স সংযোগ এবং নতুন ওয়েবসকেট সার্ভার প্রয়োজন।

এছাড়াও, সার্ভার-প্রেরিত ইভেন্টগুলিতে বিভিন্ন বৈশিষ্ট্য রয়েছে যা ওয়েবসকেটগুলির ডিজাইনের অভাব রয়েছে, যার মধ্যে স্বয়ংক্রিয় পুনঃসংযোগ, ইভেন্ট আইডি এবং নির্বিচারে ইভেন্টগুলি পাঠানোর ক্ষমতা রয়েছে।

জাভাস্ক্রিপ্ট দিয়ে একটি ইভেন্ট সোর্স তৈরি করুন

একটি ইভেন্ট স্ট্রীমে সদস্যতা নিতে, একটি 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>

এটি e.data তে "প্রথম লাইন\nসেকেন্ড লাইন" তৈরি করে। কেউ তখন 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
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 ) ব্যতীত একটি HTTP স্থিতি ফেরত দিন।

উভয় পদ্ধতিই ব্রাউজারকে সংযোগ পুনঃপ্রতিষ্ঠিত করতে বাধা দেয়।

নিরাপত্তা একটি শব্দ

ইভেন্টসোর্স দ্বারা উত্পন্ন অনুরোধগুলি অন্যান্য নেটওয়ার্ক API যেমন আনার মতো একই-অরিজিন নীতির সাপেক্ষে৷ যদি আপনার সার্ভারে SSE এন্ডপয়েন্টের প্রয়োজন হয় যাতে বিভিন্ন উৎস থেকে অ্যাক্সেস করা যায়, তাহলে পড়ুন কীভাবে ক্রস অরিজিন রিসোর্স শেয়ারিং (CORS) দিয়ে সক্ষম করবেন।