সার্ভার-প্রেরিত ইভেন্টগুলি সার্ভার থেকে ব্রাউজারে ডেটা পুশ করার সবচেয়ে সহজ উপায়। এগুলি সাধারণ HTTP-র উপর চলে, স্বয়ংক্রিয়ভাবে পুনরায় সংযোগ করে এবং কোনও অতিরিক্ত প্রোটোকলের প্রয়োজন হয় না৷ ড্যাশবোর্ড, বিজ্ঞপ্তি, অগ্রগতি আপডেট এবং স্ট্রিমিং AI প্রতিক্রিয়াগুলির জন্য — যে কোনও কিছু যেখানে ডেটা একভাবে প্রবাহিত হয় — SSE সাধারণত WebSockets-এর উপর সঠিক পছন্দ।
📋 Table of Contents
- SSE বা WebSockets?
- দ্য ওয়্যার ফরম্যাট
- সার্ভার: এক্সপ্রেসের সাথে Node.js
- ক্লায়েন্ট: প্রতিক্রিয়া
- সংযোগ বিচ্ছিন্ন হওয়ার পর পুনরায় শুরু করা হচ্ছে
- প্রমাণীকরণ
- চ্যাট ইন্টারফেসে টোকেন-বাই-টোকেন আউটপুটের পিছনের প্যাটার্ন।
- বেশিরভাগ উৎপাদন SSE ব্যর্থতা প্রক্সি বাফারিং। একা হেডার সবসময় যথেষ্ট নয়।
- দ্বিতীয় নতুন লাইন ভুলে যাওয়া।
- SSE আপনাকে স্বয়ংক্রিয় পুনঃসংযোগ এবং খুব কম কোড সহ সাধারণ HTTP-এর মাধ্যমে সার্ভার-টু-ক্লায়েন্ট স্ট্রিমিং দেয়। পাঁচটি জিনিস ঠিক করুন:
SSE বা WebSockets?
| SSE | ওয়েবসকেটস | |
|---|---|---|
| নির্দেশনা | শুধুমাত্র ক্লায়েন্টের জন্য সার্ভার | দ্বিমুখী |
| প্রোটোকল | প্লেইন HTTP | ws:// |
| এ আপগ্রেড করুন৷ স্বতঃ-পুনঃসংযোগ | বিল্ট ইন | আপনি এটি বাস্তবায়ন করেন |
| ডেটা বিন্যাস | UTF-8 পাঠ্য | পাঠ্য বা বাইনারি |
| প্রক্সি বন্ধুত্ব | সাধারণত ভালো | আপগ্রেড সমর্থন প্রয়োজন |
| জটিলতা | কম | উচ্চতর |
ব্যবহার করুনSSE লাইভ ড্যাশবোর্ড, বিজ্ঞপ্তি ফিড, কাজের অগ্রগতি, লগ টেলিং এবং টোকেন-বাই-টোকেন এআই আউটপুটের জন্য। ব্যবহার করুনওয়েবসকেটস যখন ক্লায়েন্ট ঘন ঘন বার্তা পাঠায় — চ্যাট, সহযোগী সম্পাদনা, মাল্টিপ্লেয়ার গেম।
একটি সাধারণ এবং বুদ্ধিমান হাইব্রিড: সার্ভার আপডেটের জন্য SSE, ক্লায়েন্ট অ্যাকশনের জন্য সাধারণ HTTP POST অনুরোধ। এটি একটি দ্বিতীয় প্রোটোকল ছাড়া অধিকাংশ অ্যাপ্লিকেশন কভার.
দ্য ওয়্যার ফরম্যাট
SSE হল একটি ছোট, কঠোর বিন্যাস সহ একটি সাধারণ পাঠ্য প্রবাহ।
data: hello world
event: userUpdate
data: {"id":1,"name":"Ada"}
id: 42
retry: 5000
data: message with an id and a retry hint
দুটি নিয়ম প্রায় প্রতিটি বাগ সৃষ্টি করে। প্রতিটি বার্তা শেষ হয়দিয়ে দুই নতুন লাইন এবং মাল্টি-লাইন ডেটা প্রয়োজন একটিdata: প্রতিটি লাইনে উপসর্গ, যার কারণে JSON পেলোডগুলিতে কাঁচা নতুন লাইন থাকতে হবে না।
সার্ভার: এক্সপ্রেসের সাথে Node.js
import express from 'express';
const app = express();
// Track connected clients so we can broadcast.
const clients = new Set();
app.get('/api/events', (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'Connection': 'keep-alive',
// Tell nginx not to buffer this response.
'X-Accel-Buffering': 'no',
});
// Flush headers immediately so the client's connection opens.
res.flushHeaders();
const client = { id: Date.now(), res };
clients.add(client);
send(res, { type: 'connected', at: new Date().toISOString() });
// A comment line every 25s keeps proxies from closing an idle connection.
const heartbeat = setInterval(() => {
res.write(': heartbeat\n\n');
}, 25_000);
req.on('close', () => {
clearInterval(heartbeat);
clients.delete(client);
});
});
function send(res, data, event) {
if (event) res.write(`event: ${event}\n`);
// JSON.stringify never emits a raw newline, which keeps the frame valid.
res.write(`data: ${JSON.stringify(data)}\n\n`);
}
export function broadcast(data, event) {
for (const client of clients) {
send(client.res, data, event);
}
}
app.listen(3000);
তিনটি বিবরণ এখানে গুরুত্বপূর্ণ। flushHeaders() প্রথম লেখার জন্য অপেক্ষা না করে অবিলম্বে স্ট্রীম খোলে। হৃদস্পন্দন মন্তব্য মধ্যস্থতাকারীদের একটি নিষ্ক্রিয় সংযোগ বাদ দিতে বাধা দেয়। এবংX-Accel-Buffering: no nginx কে প্রতিক্রিয়া বাফার করা থেকে থামায়, যেটি একক সবচেয়ে সাধারণ কারণ SSE স্থানীয়ভাবে কাজ করে এবং উৎপাদনে ব্যর্থ হয়।
ক্লায়েন্ট: প্রতিক্রিয়া
import { useEffect, useRef, useState } from 'react';
export function useEventStream(url) {
const [messages, setMessages] = useState([]);
const [status, setStatus] = useState('connecting');
const sourceRef = useRef(null);
useEffect(() => {
const source = new EventSource(url, { withCredentials: true });
sourceRef.current = source;
source.onopen = () => setStatus('open');
source.onmessage = (e) => {
const data = JSON.parse(e.data);
setMessages(prev => [...prev, data]);
};
// Named events need their own listener.
source.addEventListener('userUpdate', (e) => {
const data = JSON.parse(e.data);
setMessages(prev => [...prev, { ...data, kind: 'userUpdate' }]);
});
source.onerror = () => {
// EventSource reconnects on its own unless the state is CLOSED.
setStatus(source.readyState === EventSource.CLOSED ? 'closed' : 'reconnecting');
};
return () => source.close();
}, [url]);
return { messages, status };
}
export function LiveFeed() {
const { messages, status } = useEventStream('/api/events');
return (
<div>
<p>Status: {status}</p>
<ul>
{messages.map((m, i) => <li key={i}>{JSON.stringify(m)}</li>)}
</ul>
</div>
);
}
ফিরে আসছেsource.close() প্রভাব থেকে ঐচ্ছিক নয়. এটি ছাড়া, রিঅ্যাক্ট স্ট্রিক্ট মোডের ডাবল-মাউন্ট একটি অনাথ সংযোগ ছেড়ে দেয় এবং অ্যাপের চারপাশে নেভিগেট করার ফলে ব্রাউজারের প্রতি-ডোমেন সংযোগের সীমা না পৌঁছানো পর্যন্ত খোলা স্ট্রীম জমা হয় এবং সবকিছু স্টল হয়ে যায়।
সংযোগ বিচ্ছিন্ন হওয়ার পর পুনরায় শুরু করা হচ্ছে
যখন আপনি একটিid:পাঠান ক্ষেত্র, ব্রাউজার এটি সঞ্চয় করে এবং এটিকেLast-Event-IDহিসাবে ফেরত পাঠায় পুনঃসংযোগের উপর। এটি আপনাকে শুধুমাত্র যা মিস করা হয়েছে তা রিপ্লে করতে দেয়।
app.get('/api/events', (req, res) => {
// ... headers as above ...
const lastId = req.headers['last-event-id'];
if (lastId) {
for (const event of getEventsSince(Number(lastId))) {
res.write(`id: ${event.id}\n`);
res.write(`data: ${JSON.stringify(event.payload)}\n\n`);
}
}
});
এটি SSE কে সর্বোত্তম প্রচেষ্টা থেকে নির্ভরযোগ্য ডেলিভারির কাছাকাছি কিছুতে পরিণত করে, যা ব্যবহারকারীর কাছে একটি ড্রপ করা বার্তা দৃশ্যমান হয় এমন বিজ্ঞপ্তি ফিডের জন্য গুরুত্বপূর্ণ।
প্রমাণীকরণ
দেশীয়EventSource API কাস্টম শিরোনাম সেট করতে পারে না, যা টোকেন-প্রমাণিত API তৈরির লোকেদের অবাক করে। তিনটি কার্যকরী পন্থা:
কুকিজ – সহজতম। পাসwithCredentials: true এবং সেশন কুকিকে অনুরোধটি প্রমাণীকরণ করতে দিন যেমন এটি অন্য যেকোন হবে।
ক্যোয়ারী স্ট্রিং এ একটি স্বল্পস্থায়ী টোকেন — টোকেন একক-ব্যবহার হলে এবং কয়েক মিনিটের মধ্যে মেয়াদ শেষ হলেই গ্রহণযোগ্য, কারণ ইউআরএলগুলি সার্ভার লগে শেষ হয়।
একটি স্ট্রিমিং পাঠকের সাথে আনুন — সম্পূর্ণ শিরোনাম নিয়ন্ত্রণ, নিজেকে পুনঃসংযোগ বাস্তবায়নের খরচে।বাফারিং নোট করুন: একটি নেটওয়ার্ক খণ্ড একটি ফ্রেমকে অর্ধেক ভাগ করতে পারে, তাই আপনাকে অবশ্যই অবশিষ্টাংশ রাখতে হবে এবং শুধুমাত্র সম্পূর্ণ ফ্রেম পার্স করতে হবে। প্রতিটি খণ্ডকে স্বাধীনভাবে পার্স করলে মাঝে মাঝে JSON ত্রুটি তৈরি হয় যা নির্ণয়ের জন্য বেদনাদায়ক।
async function streamWithAuth(url, token, onMessage) {
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const frames = buffer.split('\n\n');
buffer = frames.pop() ?? ''; // keep the incomplete frame
for (const frame of frames) {
const line = frame.split('\n').find(l => l.startsWith('data:'));
if (line) onMessage(JSON.parse(line.slice(5).trim()));
}
}
}
স্ট্রিমিং এআই প্রতিক্রিয়া
চ্যাট ইন্টারফেসে টোকেন-বাই-টোকেন আউটপুটের পিছনের প্যাটার্ন।
প্রক্সি এবং স্থাপনার কনফিগারেশন
app.post('/api/chat', async (req, res) => {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache, no-transform',
'X-Accel-Buffering': 'no',
});
res.flushHeaders();
try {
for await (const chunk of generateResponse(req.body.prompt)) {
res.write(`data: ${JSON.stringify({ token: chunk })}\n\n`);
}
res.write('data: [DONE]\n\n');
} catch (err) {
res.write(`event: error\ndata: ${JSON.stringify({ message: err.message })}\n\n`);
} finally {
res.end();
}
});
বেশিরভাগ উৎপাদন SSE ব্যর্থতা প্রক্সি বাফারিং। একা হেডার সবসময় যথেষ্ট নয়।
এছাড়াও সচেতন থাকুন যে সার্ভারহীন প্ল্যাটফর্মগুলি প্রায়শই প্রতিক্রিয়ার সময়সীমাকে সীমাবদ্ধ করে, যা দীর্ঘস্থায়ী SSE সংযোগগুলিকে অনুপযুক্ত করে তোলে। আপনার প্ল্যাটফর্মের চারপাশে ডিজাইন করার আগে এর সীমা পরীক্ষা করুন। এবং HTTP/1.1 এর সাথে, ব্রাউজারগুলি প্রতি ডোমেনে প্রায় ছয়টি সংযোগের অনুমতি দেয় — HTTP/2 সেই সীমাবদ্ধতাকে সরিয়ে দেয়, তাই আপনি যেখানে পারেন সেখানে HTTP/2 এর মাধ্যমে SSE পরিবেশন করুন।
# nginx
location /api/events {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Connection '';
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 24h;
chunked_transfer_encoding off;
}
সাধারণ ভুল
দ্বিতীয় নতুন লাইন ভুলে যাওয়া।
বার্তাটি কখনই প্রেরণ করা হয় না এবং ক্লায়েন্ট হ্যাং হয়ে গেছে বলে মনে হয়।আনমাউন্ট করার সময় ইভেন্টসোর্স বন্ধ করা হচ্ছে না।
ব্রাউজার সীমা আঘাত না হওয়া পর্যন্ত সংযোগগুলি জমা হয়৷ডেটার ভিতরে কাঁচা নতুন লাইন।
সর্বদা পেলোডJSON.stringifyহার্টবিট নেই।
নিষ্ক্রিয় সংযোগগুলি এক বা দুই মিনিট পরে মধ্যস্থতাকারীদের দ্বারা বন্ধ হয়ে যায়।প্রক্সি বাফারিং বাকি আছে।
সবকিছু স্থানীয়ভাবে কাজ করে, তারপর প্রতিক্রিয়া শেষ না হওয়া পর্যন্ত কিছুই উৎপাদনে আসে না।দ্বিমুখী ট্রাফিকের জন্য SSE ব্যবহার করা।
যদি ক্লায়েন্ট ঘন ঘন বার্তা পাঠায়, WebSockets ব্যবহার করুন।উপসংহার
SSE আপনাকে স্বয়ংক্রিয় পুনঃসংযোগ এবং খুব কম কোড সহ সাধারণ HTTP-এর মাধ্যমে সার্ভার-টু-ক্লায়েন্ট স্ট্রিমিং দেয়। পাঁচটি জিনিস ঠিক করুন:
SSE আপনাকে স্বয়ংক্রিয় পুনঃসংযোগ এবং খুব কম কোড সহ সাধারণ HTTP-এর মাধ্যমে সার্ভার-টু-ক্লায়েন্ট স্ট্রিমিং দেয়। পাঁচটি জিনিস ঠিক করুন:প্রতিটি বার্তা দুটি নতুন লাইন দিয়ে শেষ করুন, একটি পর্যায়ক্রমিক হার্টবিট মন্তব্য পাঠান, শিরোনাম এবং এনজিনএক্স কনফিগারেশন উভয়ের সাথে প্রক্সি বাফারিং অক্ষম করুন, আপনার প্রভাব পরিষ্কারে ইভেন্টসোর্স বন্ধ করুন এবংid: সাথেLast-Event-ID যখন মিস বার্তা লক্ষ্য করা হবে. WebSockets-এর জন্য তখনই পৌঁছান যখন ক্লায়েন্টকে সত্যিকার অর্থে ঘন ঘন বার্তা পাঠাতে হবে।
🔗 Share this article
✍️ Leave a Comment