Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. Server-Sent Events
- 2.1 Format: oddiy matn oqimi
- 2.2 EventSource
- 2.3 onmessage nega jim?
- 2.4 Avtomatik qayta ulanish va Last-Event-ID
- 2.5 SSE'ni fetch bilan o'qish
- 3. WebSocket
- 3.1 Ikki tomonlama kanal
- 3.2 new WebSocket va to'rt hodisa
- 3.3 Node 24 da
- 3.4 Ulanmasdan yuborish
- 4. Ishonchli ulanish: qayta ulanish va heartbeat
- 4.1 WebSocket o'zi qayta ulanmaydi
- 4.2 Heartbeat
- 4.3 Hammasi birga
- 5. Qachon qaysi?
- 6. Hujumchi nigohi
- 7. Ko'p uchraydigan xatolar
- 7.1 onmessage va nomli SSE hodisalari
- 7.2 EventSource ni yopmaslik
- 7.3 open dan oldin send
- 7.4 Kutishsiz qayta ulanish
- 7.5 Har xabarga ishonish
- 8. Mashqlar
- 1-mashq (oson): Xabarlar hisoblagichi
- 2-mashq (o'rta): Uzilgan joydan davom etish
- 3-mashq (qiyin): Qayta ulanishni sinash
- 9. Real ishda
- Xulosa
- Manbalar
WebSocket va Server-Sent Events: server o'zi xabar yuborganda
Qisqacha: Oddiy
fetchda so'rovni har doim brauzer boshlaydi. Server o'zi xabar yuborishi kerak bo'lsa (chat, buyurtma holati, kurs narxi), doimiy ulanish ochiladi. SSE (Server-Sent Events) — serverdan brauzerga bir tomonlama matn oqimi:new EventSource(url), o'zi qayta ulanadi. WebSocket — ikki tomonlama kanal:new WebSocket("wss://..."),send()vamessagehodisasi. Faqat server gapirsa — SSE, ikkala tomon ham tez-tez gapirsa — WebSocket.
Bu darsda
- Polling, SSE va WebSocket farqini va qachon qaysi biri kerakligini tushuntira olasiz.
EventSourcebilan SSE oqimini o'qiysiz: nomli hodisalar,idva avtomatik qayta ulanish.- SSE'ni
fetch+ oqim bilan o'zingiz o'qiysiz —Authorizationsarlavhasi kerak bo'lganda. - WebSocket ochasiz, xabar yuborasiz va olasiz, uning to'rt hodisasi va holatlarini bilasiz.
- Uzilgan ulanishni eksponensial kutish bilan qayta ulaydigan va heartbeat yuboradigan kod yozasiz.
Oldin bilishingiz kerak: API klient qatlami, Streams API va javobni oqim bilan o'qish, HTTP versiyalari va real-time aloqa, Asinxron naqshlar, Custom events va EventTarget.
1. Nega bu kerak?
«Bahor» oshxonasida devorda ekran bor: yangi buyurtmalar ro'yxati. Otabek buyurtmani kassaga kiritdi — u Rustam akaning ekranida darhol paydo bo'lishi kerak.
Hozirgacha bilgan yo'limiz — ekran har 5 soniyada serverdan so'raydi: "yangi buyurtma bormi?". Buni polling (so'rab turish) deyiladi. U ishlaydi, lekin uch kamchiligi bor:
- Kechikish. Buyurtma so'rovdan 0,1 soniya keyin kelsa, ekran uni 4,9 soniyadan keyin ko'radi.
- Behuda so'rovlar. Kun bo'yi har 5 soniyada — 17 000 dan ortiq so'rov. Ularning ko'pchiligining javobi: "yangi narsa yo'q".
- Yuk. 100 ta ekran — soniyasiga 20 ta bo'sh so'rov. Server ularga ham javob beradi.
Hayotda ham shunday: taksi chaqirganingizda Yandex Go ilovasi haydovchi qayerdaligini har soniya so'rab turmaydi. Telegram yangi xabarni so'ramaydi. Ular doimiy ulanish ochadi va server yangi narsa bo'lishi bilan uni o'zi "itarib" yuboradi.
HTTP versiyalari va real-time aloqa darsida bu usullarni nazariy ko'rgan edik. Bugun ularni JavaScript'da ishlatamiz:
sequenceDiagram
participant B as Brauzer
participant S as Server
Note over B,S: Polling
B->>S: yangi bormi?
S-->>B: yo'q
B->>S: yangi bormi?
S-->>B: ha, buyurtma #7
Note over B,S: SSE
B->>S: ulanaman, xabar yuboring
S-->>B: buyurtma #8
S-->>B: buyurtma #9
Note over B,S: WebSocket
B->>S: ulanish (Upgrade)
S-->>B: buyurtma #10
B->>S: #10 qabul qilindiUch bo'limga qarang: pollingda har javobdan oldin so'rov bor. SSE'da bitta so'rovdan keyin server xohlagancha yuboradi. WebSocket'da esa ikkala tomon ham istalgan vaqtda gapiradi.
2. Server-Sent Events
2.1 Format: oddiy matn oqimi
SSE (Server-Sent Events, serverdan yuboriladigan hodisalar) — oddiy HTTP javob, faqat u tugamaydi. Sarlavhasi Content-Type: text/event-stream, tanasi — maxsus formatdagi matn. Mashq API'ning /sse manzili aynan shuni beradi. curl bilan xom ko'rinishini oldik:
retry: 3000
id: 1
event: yangilik
data: {"n":1,"matn":"1-xabar"}
: ping
id: 2
event: yangilik
data: {"n":2,"matn":"2-xabar"}
event: tugadi
data: {}
Qoidalar sodda:
- Har hodisa — bir nechta qator, oxirida bo'sh qator. Bo'sh qator — "hodisa tugadi" belgisi.
data:— hodisaning ma'lumoti (matn; ko'pincha JSON).event:— hodisa nomi. Yo'q bo'lsa —message.id:— hodisa raqami. Ulanish uzilsa, brauzer uni eslab qoladi.retry:— uzilsa, necha millisekunddan keyin qayta ulanish.:bilan boshlangan qator — izoh. Server uni "men tirikman" belgisi (ping) sifatida yuboradi — oraliq proksi-serverlar jim ulanishni uzib qo'ymasligi uchun.
Bu Streams API dagi NDJSON'ga o'xshaydi: matn oqimi, yozuvlar ajratgich bilan. Farqi — ajratgich \n emas, \n\n (bo'sh qator).
2.2 EventSource
Brauzerda SSE'ni o'qish uchun tayyor obyekt bor — EventSource. U ulanadi, formatni o'zi tahlil qiladi va har hodisani JavaScript hodisasi qilib beradi. Natijani oching — besh xabar yarim soniya oraliq bilan keladi:
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
</style>
<p id="holat" role="status">Ulanmoqda...</p>
<ul id="xabarlar"></ul>
<script>
const holat = document.getElementById("holat");
const royxat = document.getElementById("xabarlar");
const url = "https://ilmhamroh.uz/api/mashq/sse?soni=5&oraliq=500";
const manba = new EventSource(url);
manba.addEventListener("open", () => {
holat.textContent = "Ulandi";
console.log("open, readyState:", manba.readyState);
});
manba.addEventListener("yangilik", (hodisa) => {
const xabar = JSON.parse(hodisa.data);
const li = document.createElement("li");
li.textContent = `${xabar.matn} (id: ${hodisa.lastEventId})`;
royxat.append(li);
console.log("yangilik:", xabar.n);
});
manba.addEventListener("tugadi", () => {
manba.close();
holat.textContent = "Server tugatdi — ulanish yopildi";
console.log("tugadi, readyState:", manba.readyState);
});
</script>Konsolda:
open, readyState: 1
yangilik: 1
yangilik: 2
yangilik: 3
yangilik: 4
yangilik: 5
tugadi, readyState: 2Qatorma-qator:
new EventSource(url)— ulanishni darhol boshlaydi.fetchdagi kabiawaityo'q: xabarlar hodisa bo'lib keladi (addEventListenerasoslari).open— ulanish ochildi.readyState:0— ulanmoqda,1— ochiq,2— yopiq.addEventListener("yangilik", ...)— serverevent: yangilikdeb yuborgan hodisalar.hodisa.data— matn, shuning uchunJSON.parse.hodisa.lastEventId—id:maydoni.tugadi— mashq API'ning "boshqa xabar yo'q" degan hodisasi. Bizclose()chaqiramiz.
2.3 onmessage nega jim?
Ko'p qo'llanmalarda manba.onmessage = (e) => ... yoziladi. Bizning serverda u hech qachon ishlamaydi. Sabab: message — faqat nomsiz hodisalar (event: qatori yo'q). Server event: yangilik deb nom bergan bo'lsa, faqat addEventListener("yangilik") uni oladi. Chrome'da tekshirdik: onmessage va yangilik tinglovchisi birga qo'yilganda, uchala xabar faqat ikkinchisiga keldi.
Qoida: serverning hujjatiga qarang — hodisalar nomlimi yoki yo'q.
2.4 Avtomatik qayta ulanish va Last-Event-ID
EventSource ning eng qimmatli xususiyati — u ulanish uzilsa, o'zi qayta ulanadi. Yuqoridagi natijada close() ni chaqirmasak nima bo'lishini Chrome'da kuzatdik:
- Server uch xabar va
tugadini yubordi, keyin javobni yopdi. errorhodisasi keldi,readyState—0(ulanmoqda). Bu xato emas, "uzildi, qayta urinaman" degani.retry: 3000tufayli 3 soniya kutdi va qayta ulandi.- Qayta ulanishda brauzer o'zi sarlavha qo'shdi:
Last-Event-ID: 3— "men 3-xabargacha oldim". - Server 4-xabardan davom etdi.
soni=3bo'lgani uchun faqattugadiqoldi.
Shu tariqa, Wi-Fi bir lahzaga uzilsa ham, foydalanuvchi bironta xabarni yo'qotmaydi — agar server id yuborsa va Last-Event-ID ni hurmat qilsa.
Teskari tomoni: oqim tugaganini aytish uchun close() siz chaqirishingiz kerak. Aks holda EventSource har 3 soniyada abadiy qayta ulanaveradi.
Tekshirib ko'ring: Server
event:qatorisizdata: salomyubordi. Unimanba.addEventListener("message", ...)oladimi?
Javob
Ha. Nomsiz hodisaning nomi — message. onmessage va addEventListener("message", ...) bir xil ishlaydi.
2.5 SSE'ni fetch bilan o'qish
EventSource ning ikki cheklovi bor: faqat GET so'rov yuboradi va unga sarlavha qo'shib bo'lmaydi — Authorization: Bearer ham (Autentifikatsiyali so'rovlar). Cookie bilan ishlaydigan saytlarga bu muammo emas, token bilan ishlaydiganlarga — muammo.
Shuning uchun AI xizmatlari (OpenAI, Anthropic, Gemini API) SSE'ni POST so'rov javobi sifatida yuboradi va ular fetch + oqim bilan o'qiladi. Node 24 da esa EventSource umuman yo'q (faqat --experimental-eventsource bayrog'i bilan). Ikkala holatda ham yechim bitta — Streams API dagi bufer usuli, faqat ajratgich \n\n:
function hodisaniOqi(blok) {
const hodisa = { nom: "message", id: null, data: [] };
for (const qator of blok.split("\n")) {
if (qator === "" || qator.startsWith(":")) continue;
const joy = qator.indexOf(":");
const maydon = joy === -1 ? qator : qator.slice(0, joy);
const qiymat = joy === -1 ? "" : qator.slice(joy + 1).trimStart();
if (maydon === "data") hodisa.data.push(qiymat);
if (maydon === "event") hodisa.nom = qiymat;
if (maydon === "id") hodisa.id = qiymat;
}
return hodisa.data.length ? hodisa : null;
}
async function* sseOqi(url, oxirgiId) {
const sarlavhalar = oxirgiId ? { "Last-Event-ID": oxirgiId } : {};
const javob = await fetch(url, { headers: sarlavhalar });
const matnOqimi = javob.body.pipeThrough(new TextDecoderStream());
let bufer = "";
for await (const bolak of matnOqimi) {
bufer += bolak;
const bloklar = bufer.split("\n\n");
bufer = bloklar.pop();
for (const blok of bloklar) {
const hodisa = hodisaniOqi(blok);
if (hodisa) yield hodisa;
}
}
}
const url =
"https://ilmhamroh.uz/api/mashq/sse?soni=3&oraliq=300&ping=1";
for await (const h of sseOqi(url)) {
console.log(h.nom, h.id, h.data.join("\n"));
}
console.log("--- 2-xabardan keyin davom:");
for await (const h of sseOqi(url, "2")) {
console.log(h.nom, h.id, h.data.join("\n"));
}Konsolda:
yangilik 1 {"n":1,"matn":"1-xabar"}
yangilik 2 {"n":2,"matn":"2-xabar"}
yangilik 3 {"n":3,"matn":"3-xabar"}
tugadi null {}
--- 2-xabardan keyin davom:
yangilik 3 {"n":3,"matn":"3-xabar"}
tugadi null {}hodisaniOqi bitta blokni (bo'sh qatorgacha bo'lgan qismni) obyektga aylantiradi:
:bilan boshlangan qator — izoh (: ping), o'tkazib yuboriladi.?ping=1tufayli server ularni xabarlar orasida yuborgan edi — chiqishda ular yo'q.qator.indexOf(":")— birinchi ikki nuqta. Undan oldini maydon nomi, keyinini qiymat. Qiymatning boshidagi bitta bo'sh joynitrimStart()olib tashlaydi (data: {...}→{...}).data— massiv: standart bo'yicha bir hodisada bir nechtadata:qatori bo'lishi mumkin, ular\nbilan birlashtiriladi.- Ma'lumotsiz blok (
retry: 3000kabi) —null, hodisa emas.
sseOqi — Asinxron iteratsiya dagi async generator. Ikkinchi chaqiruvda Last-Event-ID: 2 ni o'zimiz yubordik — EventSource qayta ulanishda qiladigan ishni qo'lda qildik va server 3-xabardan davom etdi. fetch bo'lgani uchun bu yerga Authorization sarlavhasini ham qo'shish mumkin.
Tayyor kutubxonalar ham bor: masalan, Microsoft'ning @microsoft/fetch-event-source — xuddi shu ish va avtomatik qayta ulanish. Lekin ichida nima borligini endi bilasiz.
3. WebSocket
3.1 Ikki tomonlama kanal
SSE'da brauzer faqat tinglaydi. Chat'da esa siz ham tez-tez yozasiz; onlayn o'yinda har harakatingiz soniyasiga o'nlab marta ketadi. Har biri uchun alohida fetch — og'ir: har so'rovda sarlavhalar, ulanish.
WebSocket — brauzer va server orasidagi doimiy, ikki tomonlama kanal. Bir marta ochiladi va ikkala tomon ham istalgan vaqtda xabar yuboradi. Xuddi telefon qo'ng'irog'i: raqam bir marta teriladi, keyin ikkala tomon gapiraveradi. Polling esa — har savol uchun alohida SMS.
WebSocket oddiy HTTP so'rov bilan boshlanadi. Brauzer Upgrade: websocket sarlavhasi bilan "keling, protokolni almashtiraylik" deydi. Server 101 Switching Protocols bilan rozi bo'ladi. Shundan keyin ulanish endi HTTP emas — xabarlar kichik **freym (frame)**larda ikki tomonga oqadi. Manzil ws:// (shifrsiz) yoki wss:// (shifrlangan, https kabi) bilan boshlanadi.
3.2 new WebSocket va to'rt hodisa
echo.websocket.org — ochiq sinov serveri: unga nima yuborsangiz, o'shani qaytaradi. Kodni biror sayt ochiq turgan brauzer tabida, DevTools konsoliga yozib sinang (F12 → Console):
const soket = new WebSocket("wss://echo.websocket.org");
console.log("readyState:", soket.readyState); // 0 — ulanmoqda
soket.addEventListener("open", () => {
console.log("Ochildi, readyState:", soket.readyState);
soket.send("Salom, Bahor!");
soket.send(JSON.stringify({ tur: "buyurtma", taom: "Osh" }));
});
let soni = 0;
soket.addEventListener("message", (hodisa) => {
console.log("Keldi:", hodisa.data);
soni++;
if (soni === 3) soket.close(1000, "Xayr");
});
soket.addEventListener("close", (hodisa) => {
console.log("Yopildi:", hodisa.code, hodisa.wasClean);
});
soket.addEventListener("error", () => {
console.log("Xato — tafsiloti close'da");
});Chrome 154 da konsolda:
readyState: 0
Ochildi, readyState: 1
Keldi: Request served by 4d896d95b55478
Keldi: Salom, Bahor!
Keldi: {"tur":"buyurtma","taom":"Osh"}
Yopildi: 1000 trueBirinchi xabar — echo serverning o'z salomi: Request served by va server raqami (sizda boshqa raqam chiqadi). Keyingi ikkitasi — biz yuborganlar, qaytib keldi.
To'rt hodisa:
| Hodisa | Qachon | Ichida |
|---|---|---|
open |
ulanish o'rnatildi | endi send mumkin |
message |
serverdan xabar keldi | hodisa.data — matn yoki binar |
close |
ulanish yopildi | code, reason, wasClean |
error |
nimadir buzildi | deyarli hech narsa — doim close dan oldin keladi |
send faqat matn yoki binar ma'lumot (Blob, ArrayBuffer) yuboradi. Obyekt yubormoqchi bo'lsangiz — JSON.stringify, qabul qilganda — JSON.parse. WebSocket'da EventSource dagi kabi "hodisa nomi" yo'q. Shuning uchun xabarlarga odatda tur maydoni qo'yiladi: {tur: "buyurtma", ...}, {tur: "ping"}.
close(code, reason) — ulanishni yopadi. 1000 — "oddiy yopilish". Boshqa mashhur kodlar: 1001 — sahifa yopildi, 1006 — ulanish to'satdan uzildi (internet, server qulashi; bu kodni faqat brauzer qo'yadi).
Bu darsning natija oynalarida WebSocket ishlamaydi: saytning xavfsizlik siyosati natijaga faqat ilmhamroh.uz ga ulanishga ruxsat beradi, mashq API'da esa WebSocket yo'q. Shuning uchun DevTools konsoli.
3.3 Node 24 da
Node 22 dan boshlab WebSocket Node'da ham global — brauzerdagi bilan bir xil API. Server tomonda tayyor kutubxona (ws) kerak, lekin mijoz sifatida ulanish uchun hech narsa o'rnatish shart emas:
const soket = new WebSocket("wss://echo.websocket.org");
soket.addEventListener("open", () => soket.send("Salom, Bahor!"));
soket.addEventListener("message", (hodisa) => {
console.log("Keldi:", hodisa.data);
if (hodisa.data === "Salom, Bahor!") soket.close(1000);
});
soket.addEventListener("close", (hodisa) => {
console.log("Yopildi:", hodisa.code);
});Node 24 da konsolda:
Keldi: Request served by 4d896d95b55478
Keldi: Salom, Bahor!
Yopildi: 1000Bu blok saytda "Ishga tushir" tugmasisiz: u begona serverga bog'liq va birinchi qatori har safar boshqacha. O'z kompyuteringizda node bilan ishga tushiring.
3.4 Ulanmasdan yuborish
const soket = new WebSocket("wss://echo.websocket.org");
soket.send("Salom"); // ❌ hali ulanmaganChrome:
InvalidStateError: Failed to execute 'send' on 'WebSocket': Still in CONNECTING state.Tarjimasi: "send bajarilmadi: WebSocket hali ULANMOQDA holatida." Node 24 qisqaroq aytadi: InvalidStateError: Sent before connected. Konstruktor ulanishni faqat boshlaydi — u asinxron (Sinxron va asinxron kod). Tuzatish: birinchi send — open tinglovchisi ichida yoki soket.readyState === WebSocket.OPEN tekshiruvidan keyin.
Tekshirib ko'ring: Nega
errorhodisasida xatoning sababi ko'rsatilmaydi?
Javob
Xavfsizlik uchun. Agar brauzer "bu manzilda server yo'q" yoki "port yopiq" deb aytsa, begona sayt sizning brauzeringiz orqali ichki tarmoqni (masalan, uydagi routerni) skanerlay olardi. CORS xatosida ham shunday edi. Sababni faqat DevTools ko'rsatadi, JavaScript emas.
4. Ishonchli ulanish: qayta ulanish va heartbeat
4.1 WebSocket o'zi qayta ulanmaydi
EventSource uzilsa, o'zi qayta ulanadi. WebSocket — yo'q. Telefon metroga kirdi, Wi-Fi'dan mobil internetga o'tdi, server yangilandi — close (1006) keladi va hammasi tugaydi. Qayta ulanishni o'zingiz yozasiz.
Eng oddiy yo'l — close da darhol new WebSocket(...). Bu xavfli: server o'chiq bo'lsa, minglab foydalanuvchi soniyasiga o'nlab marta urinadi va server yoqilishi bilan yana yiqiladi. To'g'ri yo'l — Asinxron naqshlar dagi eksponensial kutish (exponential backoff): har muvaffaqiyatsiz urinishdan keyin kutish ikki barobar uzayadi, lekin chegaradan oshmaydi.
function kutishVaqti(urinish, asos = 500, maks = 30_000) {
return Math.min(maks, asos * 2 ** urinish);
}
const vaqtlar = [0, 1, 2, 3, 4, 5, 6, 7].map((u) => kutishVaqti(u));
console.log(vaqtlar.join(", "));Konsolda:
500, 1000, 2000, 4000, 8000, 16000, 30000, 30000Real ilovada yana jitter (tasodifiy siljish) qo'shiladi: kutishVaqti(urinish) * (0.5 + Math.random() / 2). Aks holda server qayta yoqilganda hamma foydalanuvchi bir vaqtda — aynan 8 soniyada — urinadi. Tasodif ularni vaqt bo'ylab tarqatadi.
4.2 Heartbeat
Ba'zan ulanish "o'lik", lekin close kelmaydi: yo'ldagi tarmoq qurilmasi (masalan, Wi-Fi router) ulanishni jimgina uzdi, ikkala tomon ham buni sezmadi. Foydalanuvchi esa xabar kelmayotganiga hayron.
Heartbeat (yurak urishi) — vaqti-vaqti bilan yuboriladigan kichik "men tirikman" xabari. WebSocket protokolining o'zida "ping" xabari bor, lekin brauzer uni JavaScript'ga bermaydi. Shuning uchun ilovalar o'z xabarini yuboradi: masalan, har 25 soniyada {"tur":"ping"}, server {"tur":"pong"} qaytaradi. Javob kelmasa — ulanish o'lik deb hisoblanadi, close() qilinadi va qayta ulanish boshlanadi. SSE'da xuddi shu ishni server yuboradigan : ping izohi bajaradi.
4.3 Hammasi birga
Avval sxema. Ulanish to'rt holat orasida aylanadi. Faqat foydalanuvchi o'zi yopganda aylanish to'xtaydi:
stateDiagram-v2
[*] --> Ulanmoqda: new WebSocket
Ulanmoqda --> Ochiq: open
Ulanmoqda --> Kutish: close
Ochiq --> Kutish: close (1006)
Kutish --> Ulanmoqda: kutishVaqti tugadi
Ochiq --> Yopildi: yop()
Yopildi --> [*]Qayta ulanuvchi o'ram brauzerda shunday ko'rinadi. U yuqoridagi kutishVaqti funksiyasidan foydalanadi — konsolda sinasangiz, avval uni yozing. Heartbeat'ni qisqa qilish uchun tashlab qoldirdik — u 3-mashqda:
function ulanuvchiSoket(url, xabarda) {
let urinish = 0;
let toxtatildi = false;
let soket = null;
function ulan() {
soket = new WebSocket(url);
soket.addEventListener("open", () => {
urinish = 0;
});
soket.addEventListener("message", (h) => xabarda(h.data));
soket.addEventListener("close", () => {
if (toxtatildi) return;
setTimeout(ulan, kutishVaqti(urinish));
urinish++;
});
}
ulan();
return {
yubor(matn) {
if (soket.readyState === WebSocket.OPEN) soket.send(matn);
},
yop() {
toxtatildi = true;
soket.close(1000);
},
};
}urinishmuvaffaqiyatliopenda0ga qaytadi — keyingi uzilish yana 500 ms dan boshlanadi.toxtatildi— foydalanuvchi o'zi yopganda (sahifadan chiqdi) qayta ulanmaslik uchun.yuborulanish ochiq bo'lmasa, xabarni jim tashlaydi. Yaxshiroq variant — navbatga qo'yib,openda yuborish.
5. Qachon qaysi?
| Polling | SSE | WebSocket | |
|---|---|---|---|
| Yo'nalish | brauzer so'raydi | server → brauzer | ikki tomonlama |
| Qayta ulanish | kerak emas | o'zi (EventSource) |
o'zingiz yozasiz |
| Format | istalgan | faqat matn | matn va binar |
| Infratuzilma | oddiy HTTP | oddiy HTTP | maxsus server qismi |
Tanlash uchun bitta savol yetadi:
- Yangilik kam va kechikish muhim emas (kunlik hisobot, 5 daqiqada bir yangilanadigan ob-havo) — polling. Oddiy va ishonchli.
- Faqat server gapiradi (bildirishnomalar, buyurtma holati, AI javobini so'zma-so'z chiqarish, birja kurslari) — SSE. Oddiy HTTP, proksi va CDN'lardan oson o'tadi, qayta ulanish tayyor.
- Ikkala tomon tez-tez gapiradi (chat, onlayn o'yin, birgalikda tahrirlash — Google Docs kabi) — WebSocket.
Bitta amaliy tafsilot. HTTP/1.1 da brauzer bitta domenga bir vaqtda ko'pi bilan 6 ta ulanish ochadi. Har SSE ulanishi shulardan birini doimiy egallaydi. Foydalanuvchi saytni 6 ta tabda ochsa, yettinchi tab hech narsa yuklay olmaydi. HTTP/2 da bu chegara ~100 ta va muammo deyarli yo'qoladi — mashq API HTTP/2 da ishlaydi.
Tekshirib ko'ring: «Bahor» saytida "Buyurtmangiz holati: tayyorlanmoqda → yo'lda → yetkazildi" sahifasi kerak. Polling, SSE yoki WebSocket?
Javob
SSE. Ma'lumot faqat serverdan keladi, mehmon hech narsa yubormaydi. SSE oddiy HTTP, EventSource o'zi qayta ulanadi va Last-Event-ID bilan bironta holat yo'qolmaydi. WebSocket bu yerda ortiqcha murakkablik. Polling ham ishlaydi, lekin holat o'zgarishini kechikib ko'rsatadi.
6. Hujumchi nigohi
Doimiy ulanish — doimiy ochiq eshik. Hujumchi uni to'rt yo'l bilan sinaydi:
- Shifrsiz
ws://. Kafedagi ochiq Wi-Fi'da kimdir trafikni o'qiydi va xabarlarni, tokenlarni ko'radi. Himoya: faqatwss://. Brauzerhttpssahifadanws://ga ulanishni o'zi bloklaydi: ChromeSecurityError: ... An insecure WebSocket connection may not be initiated from a page loaded over HTTPS.deydi. - Begona xabarni HTML qilib chiqarish. Chat xabari
<img src=x onerror=...>bo'lsa va siz uniinnerHTMLbilan chizsangiz — XSS (Kontentni o'zgartirish va XSS xavfi). Himoya: xabarnitextContentbilan chizing;JSON.parsenitryichida qiling vaturmaydonini tekshiring — kutilmagan xabarni tashlab yuboring. - Saytlararo WebSocket o'g'irlash (CSWSH). WebSocket'ga CORS qoidasi qo'llanmaydi va ulanishda cookie'lar yuboriladi. Begona sayt sizning brauzeringizda
new WebSocket("wss://bank.example/...")ochsa — bankning serveri uni siz deb biladi. Himoya serverda: ulanishdaOriginsarlavhasini tekshirish (WebSocket protokoli va ws kutubxonasi). - Token URL'da.
new WebSocket("wss://...?token=eyJ...")— keng tarqalgan, chunki WebSocket konstruktori ham sarlavha qabul qilmaydi. Lekin URL server loglarida qoladi. Himoya: cookie, yoki ulangandan keyingi birinchi xabarda token yuborish ({tur: "kirish", token}), yoki faqat bir martalik, juda qisqa muddatli "chipta" token.
7. Ko'p uchraydigan xatolar
7.1 onmessage va nomli SSE hodisalari
Server event: yangilik yuboradi, kod esa onmessage ni kutadi — hech narsa kelmaydi, xato ham yo'q. Tuzatish: addEventListener("yangilik", ...). DevTools → Network → so'rov → EventStream yorlig'ida har hodisaning nomi (Type) ko'rinadi.
7.2 EventSource ni yopmaslik
Server oqimni tugatdi, EventSource esa har 3 soniyada qayta ulanaveradi — serverda keraksiz yuk, Network panelida cheksiz so'rovlar. Tuzatish: oxirgi hodisada yoki sahifadan chiqishda close().
7.3 open dan oldin send
InvalidStateError — «Ulanmasdan yuborish» bo'limidagi xato. Tuzatish: open ichida yoki readyState tekshiruvi bilan.
7.4 Kutishsiz qayta ulanish
close → darhol new WebSocket — server o'chiq bo'lsa, brauzer soniyasiga yuzlab urinadi, telefon batareyasi tugaydi, server yoqilganda yana yiqiladi. Tuzatish: eksponensial kutish + jitter + chegara.
7.5 Har xabarga ishonish
JSON.parse(hodisa.data) try siz — bitta buzuq xabar butun tinglovchini yiqitadi. Tuzatish: try/catch va tur tekshiruvi.
8. Mashqlar
1-mashq (oson): Xabarlar hisoblagichi
Natijada /api/mashq/sse?soni=4&oraliq=300 ga EventSource bilan ulaning. Har yangilik da sahifadagi <output> dagi sonni oshiring va konsolga n ni yozing. tugadi da ulanishni yoping va konsolga Jami: 4 deb yozing.
Yechim
<p>Kelgan xabarlar: <output id="soni">0</output></p>
<script>
const soniEl = document.getElementById("soni");
const manba = new EventSource(
"https://ilmhamroh.uz/api/mashq/sse?soni=4&oraliq=300",
);
let soni = 0;
manba.addEventListener("yangilik", (hodisa) => {
soni++;
soniEl.value = soni;
console.log(JSON.parse(hodisa.data).n);
});
manba.addEventListener("tugadi", () => {
manba.close();
console.log("Jami:", soni);
});
</script>Konsolda:
1
2
3
4
Jami: 4<output> — hisob natijasi uchun HTML elementi (Guruhlash va natija elementlari); value xususiyati uning matnini o'zgartiradi.
2-mashq (o'rta): Uzilgan joydan davom etish
«SSE'ni fetch bilan o'qish» bo'limidagi sseOqi dan foydalanib, Node'da "uzilish"ni sinang: /sse?soni=5&oraliq=200 ni o'qing va 2-xabardan keyin break qiling (ulanish uziladi). Oxirgi olingan id ni saqlang va shu id bilan qayta ulanib, qolganini o'qing. Har xabarni id: matn ko'rinishida chiqaring.
Yechim
function hodisaniOqi(blok) {
const hodisa = { nom: "message", id: null, data: [] };
for (const qator of blok.split("\n")) {
if (qator === "" || qator.startsWith(":")) continue;
const joy = qator.indexOf(":");
const maydon = joy === -1 ? qator : qator.slice(0, joy);
const qiymat = joy === -1 ? "" : qator.slice(joy + 1).trimStart();
if (maydon === "data") hodisa.data.push(qiymat);
if (maydon === "event") hodisa.nom = qiymat;
if (maydon === "id") hodisa.id = qiymat;
}
return hodisa.data.length ? hodisa : null;
}
async function* sseOqi(url, oxirgiId) {
const sarlavhalar = oxirgiId ? { "Last-Event-ID": oxirgiId } : {};
const javob = await fetch(url, { headers: sarlavhalar });
const matnOqimi = javob.body.pipeThrough(new TextDecoderStream());
let bufer = "";
for await (const bolak of matnOqimi) {
bufer += bolak;
const bloklar = bufer.split("\n\n");
bufer = bloklar.pop();
for (const blok of bloklar) {
const hodisa = hodisaniOqi(blok);
if (hodisa) yield hodisa;
}
}
}
const url = "https://ilmhamroh.uz/api/mashq/sse?soni=5&oraliq=200";
let oxirgiId = null;
for await (const h of sseOqi(url)) {
if (h.nom !== "yangilik") continue;
oxirgiId = h.id;
console.log(`${h.id}: ${JSON.parse(h.data[0]).matn}`);
if (h.id === "2") break;
}
console.log("Uzildi. Oxirgi id:", oxirgiId);
for await (const h of sseOqi(url, oxirgiId)) {
if (h.nom === "tugadi") break;
console.log(`${h.id}: ${JSON.parse(h.data[0]).matn}`);
}Konsolda:
1: 1-xabar
2: 2-xabar
Uzildi. Oxirgi id: 2
3: 3-xabar
4: 4-xabar
5: 5-xabarbreak async generator'ni yopadi, u esa oqimni bekor qiladi — haqiqiy uzilish kabi. Ikkinchi ulanish Last-Event-ID: 2 yubordi va server 3 dan davom etdi. Bironta xabar yo'qolmadi va takrorlanmadi.
3-mashq (qiyin): Qayta ulanishni sinash
ulanuvchiSoket(url, xabarda, Soket = WebSocket) ni yozing: «Hammasi birga» bo'limidagi kod, faqat uchinchi parametr — soket klassi va close da konsolga Uzildi (kod), N ms dan keyin qayta yozilsin. Haqiqiy server o'rniga soxta soket bilan Node'da sinang. U EventTarget dan meros olsin (Custom events va EventTarget). Dastlabki ikki urinishda u 10 ms dan keyin close (code: 1006) yuborsin, uchinchisida esa open va bitta message ("Salom!"). Kutish uchun kutishVaqti(urinish, 100) dan foydalaning. Xabar kelganda ulanishni yoping.
Ishora: brauzer va Node'da CloseEvent va MessageEvent klasslari bor: new CloseEvent("close", { code: 1006 }), new MessageEvent("message", { data: "Salom!" }).
Yechim
let urinishlar = 0;
class SoxtaSoket extends EventTarget {
static OPEN = 1;
readyState = 0;
constructor(url) {
super();
urinishlar++;
const ochiladi = urinishlar >= 3;
setTimeout(() => {
if (!ochiladi) {
this.readyState = 3;
this.dispatchEvent(new CloseEvent("close", { code: 1006 }));
return;
}
this.readyState = 1;
this.dispatchEvent(new Event("open"));
const xabar = new MessageEvent("message", { data: "Salom!" });
this.dispatchEvent(xabar);
}, 10);
}
send() {}
close() {
this.readyState = 3;
}
}
function kutishVaqti(urinish, asos = 500, maks = 30_000) {
return Math.min(maks, asos * 2 ** urinish);
}
function ulanuvchiSoket(url, xabarda, Soket = WebSocket) {
let urinish = 0;
let toxtatildi = false;
let soket = null;
function ulan() {
soket = new Soket(url);
soket.addEventListener("open", () => {
console.log(`Ulandi (${urinish + 1}-urinish)`);
urinish = 0;
});
soket.addEventListener("message", (h) => xabarda(h.data));
soket.addEventListener("close", (h) => {
if (toxtatildi) return;
const ms = kutishVaqti(urinish, 100);
console.log(`Uzildi (${h.code}), ${ms} ms dan keyin qayta`);
urinish++;
setTimeout(ulan, ms);
});
}
ulan();
return {
yubor(matn) {
if (soket.readyState === Soket.OPEN) soket.send(matn);
},
yop() {
toxtatildi = true;
soket.close(1000);
},
};
}
const ulanish = ulanuvchiSoket("wss://example.com/oshxona", (m) => {
console.log("Xabar:", m);
ulanish.yop();
}, SoxtaSoket);Konsolda:
Uzildi (1006), 100 ms dan keyin qayta
Uzildi (1006), 200 ms dan keyin qayta
Ulandi (3-urinish)
Xabar: Salom!Soxta soket — testlarda keng ishlatiladigan usul: haqiqiy serverni o'chirib-yoqib o'tirmay, "ikki marta uzildi, keyin ulandi" holatini bir soniyada sinaymiz. Parametr Soket = WebSocket — standart qiymat bilan: brauzerda haqiqiy klass ishlatiladi, sinovda esa soxtasi beriladi. Maydon static OPEN = 1 — haqiqiy WebSocket.OPEN ga o'xshash bo'lishi uchun.
Heartbeat'ni o'zingiz qo'shib ko'ring: open da setInterval bilan har 25 soniyada {"tur":"ping"} yuboring, close da clearInterval qiling (Taymerlar). Faylni kurs/mashqlar/11/25-realtime/soket.mjs ga saqlang:
git add 11/25-realtime/soket.mjs
git commit -m "11/25: qayta ulanuvchi WebSocket va soxta soket sinovi"9. Real ishda
- AI chatlar. ChatGPT, Claude va Gemini veb-sahifalari javobni SSE bilan so'zma-so'z chiqaradi; ularning API'lari ham
stream: trueda SSE qaytaradi —fetch+ bufer bilan o'qiladi, aynan «SSE'nifetchbilan o'qish» bo'limidagi kabi. - Telegram Web, WhatsApp Web — WebSocket. Yandex Go va Uzum Tezkor'da haydovchi yoki kuryer joylashuvi ham doimiy ulanish orqali keladi.
- Kutubxonalar. Ko'p loyihalar toza WebSocket o'rniga Socket.IO ishlatadi — qayta ulanish, xonalar, tasdiqlash tayyor. Server tomonini Realtime usullari va tanlov, Server-Sent Events va Socket.IO darslarida, backend qismida quramiz.
- Tayyor xizmatlar. O'z realtime serveringiz o'rniga Pusher, Ably, Centrifugo kabi xizmatlar ham bor (Tayyor realtime xizmatlari).
- Intervyu. "WebSocket va SSE farqi?", "WebSocket uzilsa nima qilasiz?", "Nega polling yomon?" — frontend va full-stack intervyularida tez-tez so'raladi.
Xulosa
- Polling — brauzer so'rab turadi: oddiy, lekin kechikadi va behuda so'rov ko'p. Server o'zi xabar yuborishi uchun doimiy ulanish kerak.
- SSE —
text/event-stream, server → brauzer.EventSource: nomli hodisalaraddEventListenerbilan,onmessagefaqat nomsizlar uchun; o'zi qayta ulanadi vaLast-Event-IDyuboradi; tugagandaclose(). - Sarlavha yoki POST kerak bo'lsa — SSE'ni
fetch+ oqim +\n\nbufer bilan o'qing. - WebSocket — ikki tomonlama:
open,message,close,error;sendfaqatopendan keyin; obyektlar — JSON vaturmaydoni bilan. - WebSocket o'zi qayta ulanmaydi: eksponensial kutish + jitter + heartbeat.
- Faqat
wss://, xabar —textContentbilan,JSON.parse—tryichida, token — URL'da emas.
Keyingi dars: localStorage va sessionStorage — tarmoqdan brauzerning o'ziga: ma'lumotni sahifa yopilgandan keyin ham saqlash va vazifalar uchun saqlash.js.
Manbalar
- HTML Living Standard: "Server-sent events" — html.spec.whatwg.org/multipage/server-sent-events.html
- WHATWG WebSockets Standard — websockets.spec.whatwg.org; RFC 6455 "The WebSocket Protocol" — rfc-editor.org
- MDN: "Using server-sent events", "EventSource", "WebSocket", "CloseEvent.code" — developer.mozilla.org
- Node.js 24 hujjatlari: "WebSocket" (global mijoz), "--experimental-eventsource" — nodejs.org/api/globals.html
- OWASP: "WebSocket Security Cheat Sheet" (Origin tekshiruvi, CSWSH) — cheatsheetseries.owasp.org
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!