IlmHamroh
JavaScript Full-stack/25-qism. HTTP, Express va REST API1/51-dars19 daqiqa
Mundarija (24)

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 -v bu hammasini ko'rsatadi.

Bu darsda

  • Node serveriga xom HTTP matnini o'zingiz yuborib, server nimani "ko'rishini" tekshirasiz.
  • Content-Length, chunked va "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, --raw chiqishini 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:http ham, Express ham yo'q. Bunday kodni faylga (.mjs) saqlab, terminalda node fayl.mjs bilan 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 namunaviy vazifalar-api loyihasi 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-Length noto'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:

js
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:

text
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 doim req.headers["content-type"] deb yozasiz.
  • Javobda Date qatorini 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:

js
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:

js
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:

text
/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:

text
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 header

Tarjimasi: "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:

text
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\n

O'qish qoidasi:

  1. Content-Length yo'q, o'rnida Transfer-Encoding: chunked.
  2. 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".
  3. Bo'lakdan keyin yana \r\n.
  4. 0 uzunlikli bo'lak — "tana tugadi".

Bu formatni o'qish qanchalik oddiy ekanini o'zingiz ko'ring. Quyidagi funksiya saytda jonli ishlaydi:

js
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:

text
"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:

text
"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\n qatori 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:

bash
A=http://127.0.0.1:58696
curl -sv -o NUL -o NUL $A/salom $A/salom

Kanon natijasi (qisqartirilgan). Asosiy qator — o'rtadagi Reusing existing:

text
> 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 intact

Qatorlarni o'qing (curl darsidan eslang: > — yuborilgan, < — kelgan, * — curl izohi):

  • Connection: keep-alive va Keep-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:

js
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:

text
5 ta ketma-ket: 1 ta yangi ulanish
5 ta parallel: 4 ta yangi ulanish
keepAlive: false: 5 ta yangi ulanish

Uch saboq:

  1. Ketma-ket 5 so'rov — bitta ulanish. Keep-alive ishlayapti.
  2. 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.
  3. keepAlive: false — har so'rovga yangi ulanish, keyin yopiladi. Node 19 gacha http.get ning global agent'i shunday ishlardi. Hozir ham new http.Agent() sukut bo'yicha keepAlive: 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 yopadi

Diagrammada 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:http2 bilan 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:quic moduli 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:

text
POST / HTTP/1.1
Host: x
Content-Length: 4
Transfer-Encoding: chunked

Biz ishga tushirgan javob:

text
"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:

text
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-Length bormi (ha/yo'q):
Yechim
  • 10 — a hex'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 — chunked va Content-Length birga 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
js
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:

text
"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
js
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:

text
1-so'rov shu ulanishda
2-so'rov shu ulanishda
3-so'rov shu ulanishda

Uchala 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 -v dagi Reusing qatori keep-alive ishlayotganini ko'rsatadi.
  • Load balancer ortida — keepAliveTimeout noto'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-Length va Transfer-Encoding: chunked farqi?", "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) — Node Content-Length ni 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

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
HTTP server tomonda: keep-alive, chunked encoding va curl bilan tahlil — IlmHamroh