Mundarija (24)
- Bu darsda
- 1. Nega bu kerak?
- 2. Server nimani oladi: xom matn
- 2.1 So'rovni qo'lda yuboramiz
- 2.2 Tana qayerda tugaydi?
- 2.3 Chunked: uzunlik oldindan noma'lum
- 2.4 Uchinchi yo'l: ulanishni yopish
- 3. Keep-alive: bitta ulanish, ko'p so'rov
- 3.1 curl bilan ko'ramiz
- 3.2 Server o'zi sanaydi
- 3.3 Server qancha kutadi?
- 4. HTTP/2 va HTTP/3: server egasi nigohida
- 5. Status oilalari — server javobgarligi
- 6. Hujumchi nigohi: bitta so'rov — ikki xil o'qish
- 7. API qadami: hali kod yo'q
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): xom javobni o'qing
- 2-mashq (o'rta): chunked'ga o'girish
- 3-mashq (qiyin): har ulanishda nechta so'rov
- 4-mashq: API qadami — asboblarni tekshiring
- 10. Real ishda
- Xulosa
- Manbalar
HTTP server tomonda: keep-alive, chunked encoding va curl bilan tahlil
Qisqacha: Server so'rovni oddiy matn sifatida oladi: so'rov qatori, sarlavhalar, bo'sh qator va tana. Javob tanasining oxirini mijoz uch yo'l bilan biladi:
Content-Length(baytlar soni),Transfer-Encoding: chunked(bo'laklar, har birining uzunligi o'n oltilik sanoqda) yoki ulanishning yopilishi. HTTP/1.1 da ulanish sukut bo'yicha ochiq qoladi (keep-alive) — Node mijozga "5 soniya kutaman" deydi.curl -vbu hammasini ko'rsatadi.
Bu darsda
- Node serveriga xom HTTP matnini o'zingiz yuborib, server nimani "ko'rishini" tekshirasiz.
Content-Length,chunkedva "ulanishni yopish" — tana chegarasining uch usulini ajratasiz.- Keep-alive'ni server tomonidan o'lchaysiz: bitta ulanishda nechta so'rov o'tdi.
- HTTP/1.1, HTTP/2 va HTTP/3 farqini server egasi nigohida tushuntirasiz.
curl -v,-i,--rawchiqishini qatorma-qator o'qiysiz.
Oldin bilishingiz kerak: Client–server modeli va HTTP xabari, HTTP versiyalari va real-time aloqa, net, dns va tls, Built-in fetch va undici.
Maslahat: 24-qismdagidek, Node'ga xos bloklar ostida «Ishga tushir» tugmasi yo'q — saytdagi natija oynasi brauzer, unda
node:httpham, Express ham yo'q. Bunday kodni faylga (.mjs) saqlab, terminaldanode fayl.mjsbilan ishga tushiring. Ostidagi chiqishni biz Node 24.21.0 da oldik. Darsda kanon so'zi ko'p uchraydi: bu — biz kurs uchun oldindan qurib, sinab va o'lchab qo'ygan namunaviyvazifalar-apiloyihasi va uning tajribalari. "Kanon natijasi" — o'sha loyihadan olingan haqiqiy chiqish. Toza JavaScript bloklari (masalan tana bo'laklarini ochish) esa saytda jonli ishlaydi.
1. Nega bu kerak?
22-qismda vazifalar ilovasi tayyor bo'ldi, lekin ma'lumot faqat brauzerda — localStorage da. Telefoningizda qo'shgan vazifa kompyuterda ko'rinmaydi. Bu qismda uning backend'ini — vazifalar-api ni quramiz: vazifalarni saqlaydigan va HTTP orqali beradigan server. Avval Express'siz (node:http bilan), keyin Express 5 bilan, oxirida testlar bilan.
Lekin birinchi kod yozishdan oldin bitta savol: server aslida nimani oladi? Express, NestJS, Next.js Route Handler — hammasining tagida bir xil narsa bor: TCP ulanish orqali kelgan matn. Express req.body ni sehr bilan to'ldirmaydi — kimdir baytlarni o'qib, qayerda tugashini aniqlab, JSON'ga aylantiradi. Bu darsda o'sha "kimdir" ning ishini ko'ramiz.
Bu bilim real muammolarda kerak bo'ladi:
- Javob mijozda kesilib qoldi —
Content-Lengthnoto'g'ri hisoblangan. - Server "sekin" ishlaydi — har so'rov yangi ulanish ochyapti.
- Load balancer (so'rovlarni bir nechta serverga taqsimlovchi dastur) ortida vaqti-vaqti bilan 502 xatosi — keep-alive vaqtlari mos emas.
O'xshatish: pochta bo'limi. 02-qismda siz xat yuborgan odam edingiz. Endi siz — xatlarni qabul qiladigan xodimsiz: konvertni ochasiz, ichida necha varaq borligini bilishingiz kerak va bir mijozning bir nechta xatini bitta navbatda qabul qilasiz.
2. Server nimani oladi: xom matn
2.1 So'rovni qo'lda yuboramiz
net darsida TCP soket bilan matn yuborgan edik. Endi o'sha soket bilan Node'ning HTTP serveriga qo'lda yozilgan so'rov yuboramiz. Server o'zi qanday ko'rganini chiqaradi:
import http from "node:http";
import net from "node:net";
const server = http.createServer((req, res) => {
console.log(req.method, req.url, req.httpVersion);
console.log(req.headers);
res.end("salom");
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const { port } = server.address();
const socket = net.connect(port, "127.0.0.1");
socket.write(
"GET /vazifalar?bajarildi=true HTTP/1.1\r\n" +
"Host: localhost\r\n" +
"X-Mijoz: Ali\r\n" +
"Connection: close\r\n" +
"\r\n",
);
let answer = "";
for await (const chunk of socket) answer += chunk;
console.log(JSON.stringify(answer.replace(/Date: .*\r\n/, "")));
server.close();Konsolda:
GET /vazifalar?bajarildi=true 1.1
{ host: 'localhost', 'x-mijoz': 'Ali', connection: 'close' }
"HTTP/1.1 200 OK\r\nConnection: close\r\nContent-Length: 5\r\n\r\nsalom"Nimaga qarang:
- Biz yuborgan matn — HTTP xabari darsidagi to'rt qism: so'rov qatori, sarlavhalar, bo'sh qator (
\r\n\r\n), tana (bu yerda yo'q). Qator oxiri —\r\n(CRLF), qator oxirlari darsidagi Windows uslubi. HTTP/1.1 shuni talab qiladi. - Node matnni bo'laklarga ajratib berdi:
req.method,req.url(yo'l + query, domensiz),req.httpVersion. - Sarlavha nomlari kichik harfga o'tdi:
X-Mijoz→'x-mijoz'. HTTP'da sarlavha nomi katta-kichik harfni farqlamaydi, Node qulaylik uchun hammasini kichik qiladi. Kodda doimreq.headers["content-type"]deb yozasiz. - Javobda
Dateqatorini olib tashladik — u har soniyada o'zgaradi, chiqish barqaror bo'lishi uchun.
Javobga qarang: biz faqat res.end("salom") dedik, lekin Node o'zi holat qatorini (200 OK), Connection: close (mijoz shuni so'ragani uchun) va Content-Length: 5 ni qo'shdi.
2.2 Tana qayerda tugaydi?
TCP — bayt oqimi, unda "xabar tugadi" belgisi yo'q. Sarlavhalar tugashini bo'sh qator bildiradi. Tana-chi? Mijoz qachon "hammasini oldim" deyishi kerak? HTTP/1.1 da uchta javob bor:
| Usul | Mijoz qanday biladi | Qachon |
|---|---|---|
Content-Length: N |
N bayt o'qiydi | tana oldindan tayyor |
Transfer-Encoding: chunked |
bo'laklarni 0 gacha o'qiydi |
tana hali yozilyapti |
| ulanish yopilishi | soket yopilguncha o'qiydi | eski HTTP/1.0 |
Birinchisini ko'rdik. Content-Length — baytlar soni, belgilar emas. Bu farq lotin harflarida ko'rinmaydi, lekin boshqa belgilarda darhol chiqadi:
const text = "choy ✓";
console.log(text.length); // 6
console.log(new TextEncoder().encode(text).length); // 8✓ belgisi UTF-8 da 3 bayt. Agar sarlavhaga text.length (6) yozsangiz, mijoz 6 bayt o'qib to'xtaydi:
import http from "node:http";
const text = "choy ✓";
const server = http.createServer((req, res) => {
const length = req.url === "/xato"
? text.length // ❌ belgilar soni
: Buffer.byteLength(text); // ✅ baytlar soni
res.writeHead(200, {
"Content-Type": "text/plain; charset=utf-8",
"Content-Length": length,
});
res.end(text);
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const base = `http://127.0.0.1:${server.address().port}`;
for (const path of ["/xato", "/togri"]) {
const res = await fetch(base + path);
console.log(path, JSON.stringify(await res.text()));
}
server.closeAllConnections();
server.close();Konsolda:
/xato "choy �"
/togri "choy ✓"� — "buzilgan belgi" (replacement character): mijoz ✓ ning 3 baytidan faqat birini oldi. Xato jim o'tdi — server ham, mijoz ham xato bermadi. Qoida: Content-Length ni o'zingiz yozsangiz — Buffer.byteLength(text). Yaxshisi — umuman yozmang, res.end(text) ni Node o'zi to'g'ri hisoblaydi.
Biz yana bir narsani sinadik: res.strictContentLength = true bo'lsa, Node bunday javobni yubormaydi va xato tashlaydi. Biz ishga tushirgan chiqish:
ERR_HTTP_CONTENT_LENGTH_MISMATCH Response body's content-length of 8 byte(s) does not match the content-length of 6 byte(s) set in headerTarjimasi: "Javob tanasining uzunligi 8 bayt, sarlavhada esa 6 bayt yozilgan — mos emas". Jim buzilish o'rniga baland xato — sinov paytida yoqib qo'yish foydali.
2.3 Chunked: uzunlik oldindan noma'lum
Server javobni bo'lak-bo'lak yozsa-chi? Masalan, katta ro'yxatni bazadan o'qib, darhol jo'natsa? Oxirida necha bayt bo'lishini u oldindan bilmaydi. Shunda Node chunked encoding (bo'laklab uzatish) ni yoqadi. Kanon tajribasi serverga res.write bilan uch bo'lak yozdirdi va xom javobni net.Socket bilan o'qidi. Ko'rinish uchun har \r\n dan keyin qator tashlangan. Ikki narsaga qarang: Content-Length qatori yo'q va tana ichida har bo'lak oldidan bitta son turibdi:
HTTP/1.1 200 OK\r\n
Content-Type: text/plain; charset=utf-8\r\n
Date: <sana>\r\n
Connection: close\r\n
Transfer-Encoding: chunked\r\n
\r\n
10\r\n
birinchi bo'lak
\r\n
9\r\n
ikkinchi
\r\n
7\r\n
oxirgi
\r\n
0\r\n
\r\nO'qish qoidasi:
Content-Lengthyo'q, o'rnidaTransfer-Encoding: chunked.- Har bo'lak oldidan uning uzunligi — o'n oltilik (hex) sanoqda.
10— o'n emas, 16:"birinchi bo'lak\n"aynan 16 bayt.9—"ikkinchi\n",7—"oxirgi\n". - Bo'lakdan keyin yana
\r\n. 0uzunlikli bo'lak — "tana tugadi".
Bu formatni o'qish qanchalik oddiy ekanini o'zingiz ko'ring. Quyidagi funksiya saytda jonli ishlaydi:
function decodeChunked(raw) {
let rest = raw;
let body = "";
while (true) {
const lineEnd = rest.indexOf("\r\n");
const size = parseInt(rest.slice(0, lineEnd), 16);
if (size === 0) return body;
const start = lineEnd + 2;
body += rest.slice(start, start + size);
rest = rest.slice(start + size + 2); // keyingi \r\n
}
}
const raw =
"10\r\nbirinchi bo'lak\n\r\n9\r\nikkinchi\n\r\n" +
"7\r\noxirgi\n\r\n0\r\n\r\n";
console.log(JSON.stringify(decodeChunked(raw)));Konsolda:
"birinchi bo'lak\nikkinchi\noxirgi\n"parseInt(..., 16) — matnni o'n oltilik son sifatida o'qiydi (sonlar darsi). Bu o'quv modeli: haqiqiy tahlilchi baytlar bilan ishlaydi (bizda hamma belgi 1 baytli, shuning uchun satr yetdi) va xatolarni tekshiradi.
Qachon chunked chiqadi? Node qoidasi: res.write() ishlatilsa va Content-Length berilmasa. Bitta res.end(matn) — Content-Length. AI chat darsida ko'rgan oqimli javob (har 50 ms da bir so'z) — aynan chunked: brauzer har bo'lakni kelishi bilan ko'rsatadi.
2.4 Uchinchi yo'l: ulanishni yopish
Eski HTTP/1.0 mijozi chunked'ni bilmaydi. Biz o'sha serverga GET / HTTP/1.0 yubordik (server res.write("bir\n") va res.end("ikki\n") qiladi). Biz ishga tushirgan chiqish:
"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nbir\nikki\n"Na Content-Length, na chunked — tana oxirini mijoz ulanish yopilishidan biladi. Kamchiligi: ulanish bir martalik, va uzilish "hammasi keldi"mi yoki "yarmida uzildi"mi — farqlab bo'lmaydi. Shuning uchun HTTP/1.1 chunked'ni olib kirgan.
Tekshirib ko'ring: Chunked javobda
1a\r\nqatori keldi. Keyingi bo'lak necha bayt?
Javob
26 bayt. 1a — o'n oltilik: 1 × 16 + 10 (a) = 26. Uzunlik doim hex'da yoziladi, shuning uchun 10 ni ko'rsangiz — bu 16.
3. Keep-alive: bitta ulanish, ko'p so'rov
3.1 curl bilan ko'ramiz
Har so'rov uchun yangi TCP ulanish ochish qimmat: TCP darsidagi uch bosqichli salomlashish, HTTPS bo'lsa yana TLS kelishuvi. HTTP/1.1 da ulanish sukut bo'yicha ochiq qoladi — buni keep-alive deyiladi. fetch darsida buni mijoz tomonidan o'lchagan edik. Endi server tomondan qaraymiz.
Kanon bitta curl buyrug'iga bir URL'ni ikki marta berdi:
A=http://127.0.0.1:58696
curl -sv -o NUL -o NUL $A/salom $A/salomKanon natijasi (qisqartirilgan). Asosiy qator — o'rtadagi Reusing existing:
> GET /salom HTTP/1.1
> Host: 127.0.0.1:58696
> User-Agent: curl/8.19.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Content-Type: application/json; charset=utf-8
< Content-Length: 17
< Connection: keep-alive
< Keep-Alive: timeout=5
<
* Connection #0 to host 127.0.0.1:58696 left intact
* Reusing existing http: connection with host 127.0.0.1
> GET /salom HTTP/1.1
...
* Connection #0 to host 127.0.0.1:58696 left intactQatorlarni o'qing (curl darsidan eslang: > — yuborilgan, < — kelgan, * — curl izohi):
Connection: keep-alivevaKeep-Alive: timeout=5— server: "ulanishni yopmayman, 5 soniya kutaman". (Node 24 aslida yana 1 soniya zaxira kutadi — buni server timeout'lari darsida o'lchaymiz.)left intact— "ulanish butun qoldirildi".Reusing existing http: connection— ikkinchi so'rov o'sha ulanishda ketdi. Yangi TCP salomlashish yo'q.
-o NUL — tanani Windows'dagi "hech qayer" ga tashlash (Linux'da /dev/null). Port (58696) sizda boshqa bo'ladi — server listen(0) bilan ochilgan.
3.2 Server o'zi sanaydi
Har yangi TCP ulanishda serverda "connection" hodisasi chiqadi. Shu bilan uch holatni o'lchaymiz:
import http from "node:http";
const server = http.createServer((req, res) => res.end("salom"));
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const url = `http://127.0.0.1:${server.address().port}/`;
let opened = 0;
server.on("connection", () => opened++);
async function measure(label, send) {
opened = 0;
await send();
console.log(`${label}: ${opened} ta yangi ulanish`);
}
const pause = (ms) => new Promise((r) => setTimeout(r, ms));
const get = async () => (await fetch(url)).text();
await measure("5 ta ketma-ket", async () => {
for (let i = 0; i < 5; i++) {
await get();
await pause(20);
}
});
await measure("5 ta parallel", () =>
Promise.all([1, 2, 3, 4, 5].map(get)));
const agent = new http.Agent({ keepAlive: false });
await measure("keepAlive: false", async () => {
for (let i = 0; i < 5; i++) {
await new Promise((r) =>
http.get(url, { agent }, (res) => res.resume().on("end", r)));
}
});
server.closeAllConnections();
server.close();Konsolda:
5 ta ketma-ket: 1 ta yangi ulanish
5 ta parallel: 4 ta yangi ulanish
keepAlive: false: 5 ta yangi ulanishUch saboq:
- Ketma-ket 5 so'rov — bitta ulanish. Keep-alive ishlayapti.
- Parallel 5 so'rov — 4 ta yangi ulanish (+ oldingi ochiq qolgani = 5). HTTP/1.1 da bitta ulanishda bir vaqtda faqat bitta so'rov bo'ladi: javob kelmaguncha keyingisi kutadi. Shuning uchun parallel so'rovlar uchun mijoz qo'shimcha ulanish ochadi.
keepAlive: false— har so'rovga yangi ulanish, keyin yopiladi. Node 19 gachahttp.getning global agent'i shunday ishlardi. Hozir hamnew http.Agent()sukut bo'yichakeepAlive: false(biz tekshirdik) — o'z agent'ingizni yaratsangiz, buni yodda tuting.
Nega pause(20)? Biz uni olib tashlab sinadik: birinchi 5 so'rovga 2 ulanish ochildi (kanonda ham, fetch darsida ham shunday — "20 so'rov, ulanish 2"). Sabab: undici ikkinchi so'rovni birinchi ulanish "bo'shadi" deb belgilanishidan oldin jo'natadi. 20 ms pauza bilan — doim bitta. Bu mijozning ichki tafsiloti, server uchun ikkalasi ham normal.
sequenceDiagram
participant M as Mijoz
participant S as Server
M->>S: TCP ulanish (1 marta)
M->>S: GET /salom
S-->>M: 200, keep-alive, timeout=5
M->>S: GET /salom (o'sha ulanish)
S-->>M: 200
Note over M,S: 5 s (+1 s zaxira) jimlik
S-->>M: ulanishni yopadiDiagrammada asosiysi — pastki qism: mijoz jim tursa, taxminan 5–6 soniyadan keyin ulanishni server yopadi.
3.3 Server qancha kutadi?
Node serverida bir nechta "kutish" sozlamasi bor. Ularning sukut qiymatlari (biz http.createServer() dan o'qidik, Node 24.21.0):
| Sozlama | Sukut | Ma'nosi |
|---|---|---|
keepAliveTimeout |
5000 ms | javobdan keyin keyingi so'rovni kutish (+1000 ms zaxira) |
headersTimeout |
60 000 ms | sarlavhalar to'liq kelishini kutish |
requestTimeout |
300 000 ms | butun so'rovni (tana bilan) kutish |
maxRequestsPerSocket |
0 (cheksiz) | bitta ulanishda nechta so'rov |
Nega abadiy kutmaydi? Har ochiq ulanish xotira va fayl deskriptori egallaydi. 10 000 ta "jim" mijoz — 10 000 ta band resurs. 5 soniya — muvozanat: ketma-ket so'rovlar uchun yetarli, lekin unutilgan ulanishlar tez tozalanadi.
Bu raqamlar load balancer (nginx, bulut balanser) ortida muhim bo'ladi: ikki tomonning kutish vaqti mos kelmasa — sirli 502 xatolari chiqadi. Buni server timeout'lari darsida o'lchab sozlaymiz.
Tekshirib ko'ring: Mijoz 3 ta so'rovni bir vaqtda (parallel) yubordi, server HTTP/1.1. Eng kamida nechta TCP ulanish kerak, agar uchalasi bir vaqtda javob kutsa?
Javob
Uchta. HTTP/1.1 da bitta ulanishda bir vaqtda bitta so'rov-javob bo'ladi. Bitta ulanishda navbat bilan yuborish mumkin, lekin unda ikkinchisi birinchisini kutadi — "parallel" bo'lmaydi.
4. HTTP/2 va HTTP/3: server egasi nigohida
HTTP versiyalari darsida tushunchani ko'rgansiz. Server tomonidan qisqacha nima o'zgaradi:
- HTTP/1.1 — matn, bitta ulanishda bir vaqtda bitta so'rov. Yuqoridagi o'lchovda 5 parallel so'rov = 5 ulanish.
- HTTP/2 — binar freymlar, bitta ulanishda ko'p oqim (stream) — multiplexing. Kanon
node:http2bilan o'lchadi: 10 ta parallel so'rov (har biri 100 ms) — 1 ta TCP ulanish, jami 133 ms. Brauzerlar HTTP/2 ni faqat TLS ustida ishlatadi, shuning uchun sertifikat kerak — bularni HTTPS, TLS va HTTP/2 darsida qilamiz. - HTTP/3 — TCP o'rniga QUIC (UDP ustida). Biz tekshirdik: Node 24.21.0 da
node:quicmoduli yo'q (ERR_UNKNOWN_BUILTIN_MODULE), ya'ni HTTP/3 serverni Node'ning o'zi bermaydi. Amalda HTTP/3 ni server oldidagi nginx, Caddy yoki CDN beradi, Node esa ortida HTTP/1.1 da ishlaydi.
Muhim natija: so'rov va javob ma'nosi (metod, yo'l, sarlavhalar, status, tana) uchala versiyada bir xil. Sizning kodingiz (req.method, res.statusCode) versiyani deyarli sezmaydi. Shuning uchun bu qismda HTTP/1.1 bilan ishlaymiz.
5. Status oilalari — server javobgarligi
Status kodlari darsida mijoz sifatida ularni o'qigansiz. Endi ularni siz tanlaysiz:
| Oila | Ma'nosi | Kim aybdor |
|---|---|---|
| 2xx | muvaffaqiyat | — |
| 3xx | boshqa joyga qarang / o'zgarmagan | — |
| 4xx | so'rov noto'g'ri | mijoz |
| 5xx | serverda xato | biz |
Qoida: 4xx mijozga "so'rovingizni tuzating" deydi, 5xx — "bizda nosozlik, keyinroq urinib ko'ring". Mijoz xatosiga 500 qaytarish — monitoringni yolg'on signallarga to'ldiradi. Bizning xatoimizga 400 qaytarish — mijozni behuda o'z kodini qidirishga majbur qiladi.
vazifalar-api ning birinchi versiyasi 201, 204, 400, 404, 405, 413, 415, 422 va 500 ni qaytaradi. Har birini kelgusi darslarda asoslab tanlaymiz, to'liq jadval esa status kodlarni tanlash darsida.
6. Hujumchi nigohi: bitta so'rov — ikki xil o'qish
Tana chegarasi ikki yo'l bilan berilishi mumkinligi (Content-Length va chunked) — hujum uchun eshik. Server oldida proxy turibdi deylik. Hujumchi so'rovga ikkalasini ham qo'yadi. Proxy Content-Length ga ishonadi, server esa chunked ga. Ular tananing chegarasini turli joyda ko'radi: proxy uchun bitta so'rov, server uchun — ikkita. Ikkinchi "yashirin" so'rov proxy tekshiruvidan o'tmay serverga yetadi. Bu hujumning nomi — HTTP request smuggling (so'rovni yashirincha o'tkazish).
Biz Node 24 serveriga aynan shunday so'rov yubordik:
POST / HTTP/1.1
Host: x
Content-Length: 4
Transfer-Encoding: chunkedBiz ishga tushirgan javob:
"HTTP/1.1 400 Bad Request\r\nConnection: close\r\n\r\n"Node'ning HTTP tahlilchisi (llhttp) bunday so'rovni rad etadi va ulanishni yopadi — sizning kodingizgacha yetmaydi. Xuddi shunday, Host sarlavhasisiz HTTP/1.1 so'rovga ham, umuman HTTP bo'lmagan matnga (SALOM) ham Node 400 qaytardi. Xulosa: xom HTTP tahlilini o'zingiz yozmang — Node'nikini ishlating va Node'ni yangilab turing. Bunday xatolar topilganda tuzatish Node yangilanishi bilan keladi.
7. API qadami: hali kod yo'q
Bu darsda vazifalar-api papkasi ochilmaydi — bu dars kanon tajribasi va yuqoridagi o'lchovlar. Keyingi darsda loyiha boshlanadi: package.json, src/server.ts va birinchi http.createServer.
Kanon o'lchovlaridan bitta raqamni eslab qoling: keyingi darslarda yoziladigan Express'siz prototip (holat 05) GET /vazifalar/5 ga sekundiga 25 611 javob berdi (autocannon, 10 ulanish × 5 s). Band mashina, bitta o'lchov: server va yuk beruvchi bir CPU'da — raqam faqat taqqoslash uchun. Express qo'shilganda bu raqam o'zgaradi — Express'ga kirish darsida o'lchaymiz.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Tuzatish |
|---|---|---|
javob oxiri � yoki kesilgan |
Content-Length = text.length |
Buffer.byteLength yoki umuman yozmang |
ERR_HTTP_CONTENT_LENGTH_MISMATCH |
strictContentLength va noto'g'ri uzunlik |
uzunlikni baytda hisoblang |
chunked bo'lak uzunligi "noto'g'ri" |
10 ni o'nlik deb o'qish |
uzunlik hex: 10 = 16 |
PowerShell'da curl -v xato |
curl — Invoke-WebRequest taxallusi |
curl.exe -v |
Git Bash'da -o /dev/null g'alati xato |
MSYS yo'l o'girish | Windows'da -o NUL |
400 Bad Request qo'lda so'rovda |
Host yo'q yoki qator oxiri \n |
Host: va \r\n |
9. Mashqlar
Mashqlar kurs/mashqlar/25/01-http-server-tomonda/ da.
1-mashq (oson): xom javobni o'qing
Server javobi:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Connection: keep-alive
Keep-Alive: timeout=5
a
lag'mon va
6
manti
0(Qator oxirlaridagi \r\n lar ko'rsatilmagan.)
- Birinchi bo'lak uzunligi (o'nlik):
- Tana oxirini bildiruvchi bo'lak uzunligi:
- Server ulanishni necha soniya ochiq ushlaydi:
- Bu javobda
Content-Lengthbormi (ha/yo'q):
Yechim
- 10 —
ahex'da 10. Tekshiramiz:lag'mon(7) + bo'shliq (1) +va(2) = 10 bayt (apostrof'ham bitta bayt). Ikkinchi bo'lak" manti"— 6 bayt: bosh bo'shliq ham sanaladi. Ekranda bo'shliqni ko'rish qiyin — xom formatni ko'z bilan o'qishning qiyinligi shu. - 0 — nol uzunlikli bo'lak — tana tugadi.
- 5 —
Keep-Alive: timeout=5. - yo'q —
chunkedvaContent-Lengthbirga kelmaydi (kelsa — 400, «Hujumchi nigohi»).
2-mashq (o'rta): chunked'ga o'girish
encodeChunked(parts) funksiyasini yozing: satrlar massivini oladi va chunked tana matnini qaytaradi. Uzunlik — baytlarda (new TextEncoder().encode(s).length), hex'da (n.toString(16), sonlar darsi). Bo'sh satrlarni tashlab keting (aks holda 0 bo'lagi tanani muddatidan oldin tugatadi). Sinang: ["osh", "", "choy ✓"].
Yechim
function encodeChunked(parts) {
const encoder = new TextEncoder();
let out = "";
for (const part of parts) {
const size = encoder.encode(part).length;
if (size === 0) continue; // 0 — "tugadi" degani
out += `${size.toString(16)}\r\n${part}\r\n`;
}
return out + "0\r\n\r\n";
}
console.log(JSON.stringify(encodeChunked(["osh", "", "choy ✓"])));Konsolda:
"3\r\nosh\r\n8\r\nchoy ✓\r\n0\r\n\r\n""choy ✓" — 6 belgi, lekin 8 bayt, shuning uchun 8. text.length ishlatsangiz 6 chiqardi va mijoz oxirgi belgini buzib o'qirdi — «Tana qayerda tugaydi?» bo'limidagi xatoning aynan o'zi.
3-mashq (qiyin): har ulanishda nechta so'rov
Server yozing: har javobda shu ulanishda nechanchi so'rov ekanini qaytarsin (1, 2, …). Ulanishni req.socket orqali taniysiz — har ulanish uchun hisoblagichni WeakMap da saqlang (WeakMap). fetch bilan 3 ta ketma-ket so'rov (orasida 20 ms pauza) yuboring va javoblarni chiqaring.
Yechim
import http from "node:http";
const counts = new WeakMap();
const server = http.createServer((req, res) => {
const n = (counts.get(req.socket) ?? 0) + 1;
counts.set(req.socket, n);
res.end(`${n}-so'rov shu ulanishda`);
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const url = `http://127.0.0.1:${server.address().port}/`;
for (let i = 0; i < 3; i++) {
console.log(await (await fetch(url)).text());
await new Promise((r) => setTimeout(r, 20));
}
server.closeAllConnections();
server.close();Konsolda:
1-so'rov shu ulanishda
2-so'rov shu ulanishda
3-so'rov shu ulanishdaUchala so'rov bitta soketda keldi — keep-alive. WeakMap nega? Ulanish yopilganda soket obyekti kerak bo'lmay qoladi, WeakMap uni xotirada ushlab turmaydi. Oddiy Map bilan har yopilgan ulanish xotirada qolardi — uzoq yashaydigan serverda bu xotira sizishi.
4-mashq: API qadami — asboblarni tekshiring
Keyingi darsda loyiha boshlanadi. Terminalda tekshiring: node --version (24 yoki yangi), curl.exe --version (Windows) yoki curl --version. Keyin istalgan Node serveringizni (masalan 3-mashqdagi, lekin server.close() siz va listen(3000) bilan) ishga tushirib, curl -v http://127.0.0.1:3000/ http://127.0.0.1:3000/ chiqishida Reusing existing qatorini toping.
Yechim
Kanon mashinasida: Node 24.21.0, curl 8.19.0. curl chiqishida birinchi so'rovdan keyin left intact, ikkinchisidan oldin Reusing existing http: connection with host 127.0.0.1 bo'ladi. Javob tanasi: 1-so'rov shu ulanishda, keyin 2-so'rov shu ulanishda — bu ham ulanish bitta ekanining isboti. Serverni Ctrl+C bilan to'xtating.
10. Real ishda
- Oqimli javoblar — AI chat, katta CSV eksport, log oqimi: hammasi chunked. Javobni oxirigacha yig'ib yuborish o'rniga bo'lak-bo'lak yozish xotirani tejaydi va foydalanuvchi natijani tezroq ko'radi.
- "Sayt sekin" tashxisi — DevTools Network'dagi "Connection ID" ustuni yoki
curl -vdagiReusingqatori keep-alive ishlayotganini ko'rsatadi. - Load balancer ortida —
keepAliveTimeoutnoto'g'ri bo'lsa 502; bu backend intervyularidagi mashhur savol. - Xavfsizlik — request smuggling har yili yangi variantlari bilan chiqadi; Node, nginx va proxy'larni yangilab turish — himoyaning bir qismi.
- Intervyuda: "
Content-LengthvaTransfer-Encoding: chunkedfarqi?", "keep-alive nima beradi?", "HTTP/2 multiplexing nima?".
Xulosa
- Server so'rovni matn sifatida oladi; Node uni
req.method,req.url,req.headers(kichik harfli nomlar) ga ajratadi. - Tana oxiri:
Content-Length(baytlar!),chunked(hex uzunlikli bo'laklar,0— oxiri) yoki ulanish yopilishi. res.end(matn)— NodeContent-Lengthni o'zi qo'yadi;res.write— chunked.- Keep-alive: ketma-ket so'rovlar bitta ulanishda; Node mijozga 5 s deydi (
keepAliveTimeout), o'zi yana 1 s zaxira kutadi. - HTTP/1.1 da bitta ulanishda bir vaqtda bitta so'rov; HTTP/2 — multiplexing, HTTP/3 — QUIC (Node 24 da yo'q).
- Ikkala chegara sarlavhasi bitta so'rovda — smuggling urinishi; Node 400 qaytaradi.
Keyingi dars: http.createServer: req va res — vazifalar-api loyihasini ochamiz va framework'siz birinchi serverni yozamiz.
Manbalar
- RFC 9112 — HTTP/1.1 (2022-06): xabar tanasi uzunligi (§6.3), chunked (§7.1), ulanishni saqlash (§9.3): https://www.rfc-editor.org/rfc/rfc9112
- RFC 9113 — HTTP/2 (2022-06): https://www.rfc-editor.org/rfc/rfc9113 · RFC 9114 — HTTP/3 (2022-06): https://www.rfc-editor.org/rfc/rfc9114
- Node.js 24 hujjati — HTTP (
keepAliveTimeout,headersTimeout,requestTimeout,strictContentLength,requireHostHeader): https://nodejs.org/docs/latest-v24.x/api/http.html - PortSwigger Web Security Academy — HTTP request smuggling: https://portswigger.net/web-security/request-smuggling
- Kanon:
natijalar/tajriba/t01-http.txt(Node 24.21.0, curl 8.19.0, Windows 11, 2026-10-24) — xom chunked javob,curl -svkeep-alive, sukut timeout'lar. Xom so'rov, bayt uzunligi,strictContentLength, HTTP/1.0, ulanishlarni sanash, smuggling vanode:quic— biz ishga tushirdik (Node 24.21.0, Windows 11, 2026-10-24).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!