IlmHamroh
JavaScript Full-stack/25-qism. HTTP, Express va REST API7/51-dars18 daqiqa
Mundarija (29)

Node.js server timeout'lari: keepAliveTimeout, headersTimeout, Slowloris va graceful shutdown

Qisqacha: Node HTTP serverida uchta asosiy timeout bor. keepAliveTimeout (sukut 5 s) — javobdan keyin bo'sh ulanish qancha ochiq turadi. U load balancer'ning bo'sh ulanish vaqtidan (~60 s) katta bo'lmasa, sirli 502 xatolar chiqadi. headersTimeout (sukut 60 s) — sarlavhalar shu vaqtda kelmasa 408 javobi. Bu Slowloris hujumiga qarshi himoya. requestTimeout (sukut 300 s) — butun so'rov uchun chegara. Serverni to'xtatishda server.close() yolg'iz yetmaydi. Biz o'lchadik: bo'sh keep-alive ulanish tufayli close() 7.4 s kutdi, closeIdleConnections() bilan esa 1.4 s.

Bu darsda

  • Uchta timeout'ni farqlaysiz va har biri qaysi muammoni hal qilishini bilasiz.
  • Load balancer ortidagi "sirli 502" ning sababini tushuntirasiz.
  • Slowloris hujumini o'z serveringizda ko'rasiz va to'xtatasiz.
  • Node 24 dagi keepAliveTimeoutBuffer ni o'lchov bilan ko'rasiz.
  • vazifalar-api ga timeout'lar va graceful shutdown qo'shib, holat 07 ni commit qilasiz.

Oldin bilishingiz kerak: HTTP server tomonda: keep-alive va chunked, http.createServer, process obyekti va signallar, timers chuqur.

1. Nega bu kerak?

Tasavvur qiling: vazifalar-api serverda ishlayapti, oldida load balancer (LB) bor. Kuniga bir necha marta foydalanuvchi 502 Bad Gateway oladi. Loglarda xato yo'q. Qayta urinsa — ishlaydi. Bu xato oylab "tasodif" deb yuradi. Aslida sababi bitta raqamda: Node bo'sh ulanishni LB'dan oldin yopadi.

Ikkinchi holat: kimdir serveringizga yuzlab ulanish ochadi va har biriga juda sekin, bittadan sarlavha qatori yuboradi. Server har birini kutadi va yangi foydalanuvchilarga joy qolmaydi. Bu — Slowloris hujumi.

Uchinchisi: yangi versiyani chiqaryapsiz va eski jarayon to'xtaydi. Ayni paytda kimdir vazifa saqlayotgan edi — so'rovi uzilib ketdi.

Uchala holat ham — timeout (kutish chegarasi) masalasi. O'xshatish: oshxona ofitsianti. Mehmon menyuni 10 daqiqa o'qisa — kutadi. Bir soat o'qisa — boshqa mehmonga o'tadi (headersTimeout). Mehmon ovqatdan keyin stolda o'tiraversa — choy yana kerakmi deb so'raydi, keyin stolni bo'shatadi (keepAliveTimeout). Oshxona yopilayotganda yangi mehmon kirmaydi, lekin o'tirganlar ovqatini tugatadi (graceful shutdown).

2. Uchta timeout

2.1 Sukut qiymatlari

Kanon tajribasi Node 24.21.0 da sukut qiymatlarni chiqardi:

Sozlama Sukut Nimani kutadi
keepAliveTimeout 5 000 ms javobdan keyin bo'sh ulanishda keyingi so'rovni
headersTimeout 60 000 ms so'rovning sarlavhalari to'liq kelishini
requestTimeout 300 000 ms butun so'rov (sarlavha + tana) kelishini
connectionsCheckingInterval 30 000 ms Node muddati o'tgan ulanishlarni qanchada tekshiradi

Hammasi createServer ning birinchi argumentida beriladi:

js
const server = createServer({
  keepAliveTimeout: 65_000,
  headersTimeout: 10_000,
  requestTimeout: 30_000,
}, ishlovchi);

E'tibor bering: headersTimeout va requestTimeout — mijoz yuborishi uchun vaqt. Sizning ishlovchingiz qancha ishlashini ular cheklamaydi. Sekin bazaga so'rov uchun alohida timeout kerak (masalan AbortSignal.timeout, timers darsi).

Oxirgi qatorga ham qarang: Node har ulanishni har millisekundda tekshirmaydi. U 30 soniyada bir marta "muddati o'tganlar"ni qidiradi. Demak headersTimeout: 10_000 amalda 10–40 s bo'lishi mumkin. Buni pastda o'lchaymiz.

2.2 Ulanish hayoti

sequenceDiagram
  participant M as Mijoz
  participant S as Node server
  M->>S: TCP ulanish
  Note over S: headersTimeout boshlandi
  M->>S: GET /vazifalar + sarlavhalar
  Note over S: requestTimeout (tana bilan)
  S-->>M: 200 OK, Keep-Alive: timeout=5
  Note over S: keepAliveTimeout boshlandi
  M->>S: keyingi so'rov (shu ulanishda)
  S-->>M: 200 OK
  Note over S: 5 s + 1 s bo'sh — ulanish yopiladi

Diagrammadagi har Note — bitta taymer. Birinchi ikkitasi mijoz yuborayotganda ishlaydi, oxirgisi — so'rovlar orasidagi bo'sh vaqtda.

3. headersTimeout va Slowloris

3.1 Hujum

Slowloris — 2009 yilda tasvirlangan hujum. Hujumchi ulanish ochadi va sarlavhalarni juda sekin, bittadan yuboradi, lekin so'rovni hech qachon tugatmaydi (sarlavhalar oxiridagi bo'sh qator kelmaydi). Server "hali kelyapti" deb kutadi. Bunday ulanishlar yuzta bo'lsa — server yangi mijozlarni qabul qila olmaydi. Hujumchiga katta internet ham, ko'p kompyuter ham kerak emas.

3.2 O'z serverimizda

Quyidagi blok kichik server ochadi (headersTimeout: 2000) va unga o'zi Slowloris bilan hujum qiladi: har 400 ms da bitta sarlavha qatori. node:net — xom TCP soket (net darsi), chunki fetch sarlavhalarni bittalab yubora olmaydi:

js
import { createServer } from "node:http";
import { connect } from "node:net";

const server = createServer({
  headersTimeout: 2000, // sarlavhalar 2 s ichida kelsin
  connectionsCheckingInterval: 500, // har 0.5 s tekshirish
}, (req, res) => res.end("ok"));
await new Promise((ok) => server.listen(0, "127.0.0.1", ok));

const t0 = performance.now();
const soket = connect(server.address().port, "127.0.0.1");
soket.write("GET / HTTP/1.1\r\nHost: x\r\n");
// Slowloris: har 400 ms da bitta "sarlavha", oxiri hech kelmaydi
const iv = setInterval(() => soket.write("X-Sekin: 1\r\n"), 400);
let javob = "";
soket.on("data", (b) => {
  javob += b;
  clearInterval(iv); // javob keldi — yozishni to'xtatamiz
});
soket.on("error", () => {}); // yopilishdagi ECONNRESET
await new Promise((ok) => soket.on("close", ok));
clearInterval(iv);
const s = Math.round((performance.now() - t0) / 1000);
console.log(`~${s} s dan keyin yopildi:`, javob.split("\r\n")[0]);
server.closeAllConnections();
server.close();

Konsolda:

text
~2 s dan keyin yopildi: HTTP/1.1 408 Request Timeout

408 Request Timeout — "so'rovni kutish vaqti tugadi". Server javob yozdi va ulanishni yopdi. Kanondagi aniq o'lchov — 2 055 ms. Xuddi shu hujum sukut sozlamalarda (60 s, tekshiruv har 30 s) 5 soniya ichida umuman yopilmadi.

3.3 Tekshiruv oralig'i: 10 s emas, 13.4 s

Kanondagi vazifalar-api (holat 07) headersTimeout: 10_000 bilan, lekin connectionsCheckingInterval sukut (30 s) bilan ishga tushdi. Jonli Slowloris (har 1 s da sarlavha):

text
Slowloris: server 13373 ms da yopdi, javob: "HTTP/1.1 408 Request Timeout"

10 s emas, 13.4 s. Node muddati o'tgan ulanishni keyingi tekshiruvda topdi. Eng yomon holatda bu 10 + 30 = 40 s bo'lishi mumkin. Kanon tekshiruv oralig'ini o'zgartirmadi: 13–40 s ham sukut 60–90 s dan ancha yaxshi, tez-tez tekshirish esa CPU oladi.

Diqqat: headersTimeout — bitta ulanishga qarshi himoya. Minglab ulanish bilan keladigan hujumni u to'xtatmaydi. Bunga qarshi ulanishlar sonini cheklash va proxy (nginx client_header_timeout, limit_conn) kerak. Proxy ortidagi Node'ga Slowloris odatda yetib kelmaydi — proxy so'rovni to'liq olgandan keyin uzatadi.

3.4 requestTimeout — sekin tana

Sarlavhalar tez kelib, tana sekin kelsa-chi? Masalan, Content-Length: 100 va har 300 ms da bitta bayt. Bu holatni requestTimeout ushlaydi. Kanon: requestTimeout: 2000 — 2 049 ms da 408. vazifalar-api da 30 s: 16 KB lik JSON uchun bu juda ko'p, lekin sekin mobil internetdagi foydalanuvchini uzib qo'ymaydi.

Tekshirib ko'ring: Ishlovchingiz bazadan javobni 2 daqiqa kutadi, requestTimeout esa 30 s. Mijoz 408 oladimi?

Javob

Yo'q. requestTimeout mijoz so'rovni yuborib bo'lishi uchun vaqt. So'rov to'liq kelgach, taymer to'xtaydi. Ishlovchi qancha ishlashi uni qiziqtirmaydi. Bazaga so'rovga o'z timeout'ingizni qo'ying.

4. keepAliveTimeout va sirli 502

4.1 Keep-alive eslatmasi

HTTP server tomonda darsida ko'rgansiz: javobdan keyin ulanish yopilmaydi, keyingi so'rov uchun saqlanadi (Connection: keep-alive). Server javobga Keep-Alive: timeout=5 yozadi: "5 soniya bo'sh tursang — yopaman".

4.2 Node 24 da +1 soniya

Biz ishga tushirdik — keepAliveTimeout: 1000, xom soket bilan bitta so'rov, keyin jim kutamiz:

js
import { createServer } from "node:http";
import { connect } from "node:net";

const server = createServer({ keepAliveTimeout: 1000 },
  (req, res) => res.end("ok"));
await new Promise((ok) => server.listen(0, "127.0.0.1", ok));
console.log("bufer:", server.keepAliveTimeoutBuffer, "ms");

const soket = connect(server.address().port, "127.0.0.1");
let javob = "";
let t0 = 0;
soket.on("data", (b) => {
  javob += b;
  if (javob.endsWith("ok")) t0 = performance.now(); // javob tugadi
});
soket.write("GET / HTTP/1.1\r\nHost: x\r\n\r\n");
await new Promise((ok) => soket.on("close", ok));
console.log(/Keep-Alive: .*/.exec(javob)[0].trim());
const s = Math.round((performance.now() - t0) / 1000);
console.log(`bo'sh ulanish ~${s} s dan keyin yopildi`);
server.closeAllConnections();
server.close();

Konsolda:

text
bufer: 1000 ms
Keep-Alive: timeout=1
bo'sh ulanish ~2 s dan keyin yopildi

Server mijozga "1 s" dedi, lekin o'zi 2 s da yopdi. Node 24 da keepAliveTimeoutBuffer (sukut 1000 ms; Node hujjatiga ko'ra v24.6.0 da qo'shilgan) qo'shiladi. Kanon aniq o'lchadi: 1000 → 2 014 ms, 3000 → 4 012 ms. Mantiq: mijoz "1 s" ni eshitib, o'zi o'z vaqtida yopsin. Server esa biroz ko'proq kutsin — shunda ikkalasi bir vaqtda harakat qilib to'qnashmaydi. Keyingi bo'limdagi muammo aynan shu to'qnashuv.

4.3 502 qanday tug'iladi

Load balancer (nginx, AWS ALB) ham Node'ga ulanishlarni qayta ishlatadi va ular uchun o'z bo'sh vaqti bor — odatda ~60 s. Node'da esa sukut 5 s. Endi vaqt chizig'i:

sequenceDiagram
  participant LB as Load balancer (60 s)
  participant N as Node (5 s)
  LB->>N: so'rov 1
  N-->>LB: 200
  Note over N: 6 s bo'sh — Node ulanishni yopadi
  LB->>N: so'rov 2 (LB uchun ulanish hali "tirik")
  N--xLB: ulanish yopilgan (ECONNRESET)
  Note over LB: mijozga 502 Bad Gateway

LB ulanishni hali yaroqli deb hisoblaydi va yangi so'rovni yuboradi. Aynan shu millisekundda Node uni yopayotgan bo'ladi. So'rov bo'shliqqa ketadi, LB foydalanuvchiga 502 qaytaradi. Kamdan-kam va tasodifiy — shuning uchun topish qiyin.

Qoida: ulanishni har doim mijoz tomon birinchi yopsin. Node uchun mijoz — LB. Demak Node'ning keepAliveTimeout i LB'nikidan katta bo'lsin. vazifalar-api da 65 000 ms (60 + zaxira). nginx'ning keepalive_timeout sukuti 75 s, lekin u brauzer tomonidagi ulanish uchun. Upstream (Node) tomonida alohida sozlama bor — upstream blokidagi keepalive_timeout, sukuti 60 s (nginx hujjati). Kanonda nginx yo'q — bu qism sinalmagan, hujjat bo'yicha. Aniq qiymatni har doim o'z LB hujjatingizdan oling.

Tekshirib ko'ring: keepAliveTimeout: 65_000 qo'yildi. Har bo'sh ulanish endi 66 s xotira va fayl deskriptorini band qiladi. Bu xavfli emasmi?

Javob

Proxy ortida — yo'q. Node'ga faqat LB ulanadi, u esa o'nlab doimiy ulanishni qayta ishlatadi. Minglab brauzer to'g'ridan ulanadigan bo'lsa, uzun keep-alive resurs yeydi. Shuning uchun bu qiymat "proxy ortidagi Node" uchun to'g'ri, to'g'ridan internetga qaragan server uchun emas.

5. Serverni to'g'ri to'xtatish

5.1 server.close() nima qiladi

Graceful shutdown (ehtiyotkor to'xtash) — yangi ulanish qabul qilinmaydi, boshlangan so'rovlar oxirigacha bajariladi, keyin jarayon chiqadi. server.close(cb) birinchi qismini qiladi: tinglashni to'xtatadi. cb esa hamma ulanish yopilgach chaqiriladi.

Tuzoq — bo'sh keep-alive ulanishlar. Node hujjatiga ko'ra, Node 19 dan beri close() chaqirilgan paytda bo'sh turgan ulanishlarni o'zi yopadi. Lekin o'sha paytda ishlayotgan ulanish keyinroq bo'shasa — uni yopmaydi, kutadi. Kanon o'lchovi aynan shu holat: bitta 1.5 s lik so'rov davom etayotganda close() chaqirildi. Chiqishda har qatorning oxiridagi callback ... ms raqamiga qarang:

text
4a. close() dan keyin yangi ulanish — ECONNREFUSED
4a. faqat server.close(): uzoq so'rov — 200 uzoq, Connection: keep-alive (1509 ms); bo'sh keep-alive ulanish yopildi null; close() callback 7405 ms dan keyin
4b. close() dan keyin yangi ulanish — ECONNREFUSED
4b. close() + har 100 ms closeIdleConnections(): uzoq so'rov — 200 uzoq, Connection: keep-alive (1508 ms); bo'sh keep-alive ulanish yopildi null; close() callback 1414 ms dan keyin

Ikkala holatda yangi ulanish rad etildi va uzoq so'rov 200 bilan tugadi. Farq oxirida: faqat close() bilan callback 7 405 ms kutdi. Uzoq so'rov javobidan keyin ulanish bo'sh keep-alive ga aylandi va Node uni 5 + 1 s ushlab turdi. closeIdleConnections() ni har 100 ms chaqirsak — 1 414 ms, ya'ni uzoq so'rov tugashi bilan.

Ikkinchi yordamchi — closeAllConnections(): hamma, jumladan ishlayotgan ulanishlarni uzadi. U oxirgi chora: "10 s kutdim, endi majburan".

5.2 yop() funksiyasi

Uchala usulni birlashtiramiz. Biz ishga tushirdik — kanondagi yop() va testdagi stsenariy (keep-alive bo'sh ulanish + 300 ms lik so'rov):

js
import { createServer, request } from "node:http";

function yop(server, kutishMs) {
  return new Promise((resolve) => {
    const iv = setInterval(() => server.closeIdleConnections(), 100);
    const majburiy = setTimeout(() => {
      server.closeAllConnections();
      tugat(false);
    }, kutishMs);
    function tugat(ozVaqtida) {
      clearInterval(iv);
      clearTimeout(majburiy);
      resolve(ozVaqtida);
    }
    server.close(() => tugat(true));
    server.closeIdleConnections();
  });
}

const server = createServer((req, res) => {
  const ms = req.url === "/uzoq" ? 300 : 0;
  setTimeout(() => res.end("tugadi"), ms);
});
await new Promise((ok) => server.listen(0, "127.0.0.1", ok));
const asos = `http://127.0.0.1:${server.address().port}`;

await (await fetch(asos)).text(); // keep-alive ulanish bo'sh qoldi
const uzoq = fetch(`${asos}/uzoq`).then((r) => r.text());
await new Promise((ok) => setTimeout(ok, 50));

const t0 = performance.now();
const yopildi = yop(server, 2000);
request(asos).on("error", (x) => console.log("yangi:", x.code)).end();
console.log("uzoq so'rov:", await uzoq);
console.log("o'z vaqtida:", await yopildi);
console.log("1 s dan tez:", performance.now() - t0 < 1000);

Konsolda:

text
yangi: ECONNREFUSED
uzoq so'rov: tugadi
o'z vaqtida: true
1 s dan tez: true

Uch kafolat: yangi ulanish rad etildi (ECONNREFUSED — "ulanish rad etildi"), boshlangan so'rov tugadi, va bo'sh keep-alive ulanish uchun 6 s kutilmadi.

5.3 Signallar

Server qachon to'xtashi kerakligini tashqaridan signal aytadi (process va signallar darsi): Docker va systemd — SIGTERM, terminalda Ctrl+C — SIGINT. vazifalar-api ning server.ts ida:

ts
let yopilmoqda = false;
async function toxta(signal: string): Promise<void> {
  if (yopilmoqda) return;
  yopilmoqda = true;
  console.log(`${signal}: yangi ulanishlar qabul qilinmaydi, …`);
  const ozVaqtida = await yop(server, 10_000);
  console.log(ozVaqtida
    ? "to'xtadi" : "10 s o'tdi — majburan uzildi");
  process.exitCode = ozVaqtida ? 0 : 1;
}
process.once("SIGTERM", () => void toxta("SIGTERM"));
process.once("SIGINT", () => void toxta("SIGINT"));

process.exit() chaqirilmaydi — exitCode qo'yiladi, Node esa event loop bo'shagach o'zi chiqadi. Majburiy exit() hali yozilayotgan logni kesib qo'yishi mumkin. yopilmoqda bayrog'i ikkinchi signalda ikkinchi marta yopishni boshlamaslik uchun.

Halol eslatma: haqiqiy SIGTERM kanonda sinalmagan. Windows'da boshqa jarayondan yuborilgan signal Node'dagi ishlovchini chaqirmaydi (process darsida ko'rgansiz). Docker va Linux'da sinash — deploy qismida. Hozircha yop() testlar bilan tekshirilgan.

6. Hujumchi nigohi

Hujum Sukut sozlamada Himoya
Slowloris (sekin sarlavha) 60–90 s ushlaydi headersTimeout 10 s; proxy
Sekin tana (R.U.D.Y.) 300 s gacha requestTimeout 30 s; tana hajmi chegarasi (tana o'qish darsi)
Minglab bo'sh ulanish har biri resurs proxy, ulanishlar chegarasi
Deploy paytidagi uzilish yarim saqlangan ma'lumot graceful shutdown, yop()

R.U.D.Y. (R-U-Dead-Yet) — Slowloris'ning tana varianti: sarlavhalar normal, Content-Length katta, tana esa bittalab bayt. requestTimeout va tana chegarasi birga ishlaydi.

7. API qadami: timeout'lar va graceful shutdown (holat 07, commit)

Holat 05 da server.ts createServer va listen ni o'zi qilardi. Endi uni ikkiga ajratamiz.

src/ishga-tushir.ts — serverni yaratadi va tinglaydi (testlar ham shuni ishlatadi):

ts
export async function ishgaTushir(
  { port = 0, host = "127.0.0.1" } = {},
) {
  const server = createServer(
    {
      // LB (nginx, ALB) bo'sh ulanishni ~60 s ushlaydi — Node undan
      // KEYIN yopsin, aks holda mijoz 502 oladi
      keepAliveTimeout: 65_000,
      // Slowloris: sarlavhalar 10 s ichida to'liq kelmasa — 408
      headersTimeout: 10_000,
      // butun so'rov (tana bilan) 30 s ichida
      requestTimeout: 30_000,
    },
    ilovaYarat({ ombor: new XotiraOmbori(), publicPapka }),
  );
  await new Promise<void>((resolve, reject) => {
    server.once("error", reject); // EADDRINUSE va h.k.
    server.listen(port, host, () => {
      server.off("error", reject);
      resolve();
    });
  });
  const { port: haqiqiy } = server.address() as AddressInfo;
  return { server, manzil: `http://${host}:${String(haqiqiy)}` };
}

(Kanon, izohlar qisqartirilgan; publicPapka asl kodda join(import.meta.dirname, "..", "public").) Ikki detal: listen xatosi (port band — EADDRINUSE) Promise'ni rad etadi, jim yiqilmaydi. PORT=0 bo'lsa, haqiqiy port address() dan olinadi.

src/yopish.ts — yuqoridagi yop(server, kutishMs) (TypeScript turlari bilan). src/server.ts — ishgaTushir + signallar.

Yangi test fayli — test/yopish.test.ts, unda ikki test. (1) bo'sh keep-alive + 300 ms so'rov: yangi ulanish ECONNREFUSED, so'rov tugaydi, yop() 1.5 s dan tez; (2) hech qachon javob bermaydigan ishlovchi — 200 ms da majburan uziladi, false, mijozda fetch failed.

Jonli tekshiruvda javob sarlavhasi ham o'zgardi (kanon, holat 07):

text
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Content-Length: 144
Location: /vazifalar/1
Connection: keep-alive
Keep-Alive: timeout=65

Holat 05 da timeout=5 edi. Tekshiruv: tip 0, lint 0, testlar 7/7; sovuq start 171 ms; yuk (bitta mashina, bitta o'lchov) GET /vazifalar 2 669 req/s, GET /vazifalar/5 27 096 req/s — timeout'lar tezlikka ta'sir qilmadi.

bash
git add .
git commit -m "Server timeout'lari (keepAliveTimeout 65 s, headersTimeout 10 s) va graceful shutdown"

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
LB ortida tasodifiy 502 keepAliveTimeout < LB bo'sh vaqti Node'niki kattaroq (65 s)
Deploy'da 10 s "osilish" close() keyin bo'shagan keep-alive'ni kutadi closeIdleConnections()
Deploy'da so'rovlar uzildi process.exit() darhol yop() + exitCode
headersTimeout 10 s, lekin 40 s ishlaydi tekshiruv oralig'i 30 s kutilgan xulq; proxy
Error: listen EADDRINUSE port band boshqa port yoki eski jarayonni to'xtatish
Uzoq ishlovchi baribir to'xtamaydi requestTimeout ishlovchini cheklamaydi ichki timeout (AbortSignal.timeout)

listen EADDRINUSE: address already in use 127.0.0.1:3000 — "manzil allaqachon band". Odatda oldingi server hali ishlayapti.

9. Mashqlar

Yechimlar kurs/mashqlar/25/07-timeout/ da.

1-mashq (oson): to'ldiring

  • Sarlavhalar o'z vaqtida kelmasa server qaytaradigan status —
  • Bo'sh keep-alive ulanishlarni darhol yopadigan metod — server.()
  • Node 24 da keepAliveTimeout ga qo'shiladigan bufer — ms
  • LB ortida Node'ning keepAliveTimeout i LB'nikidan bo'lishi kerak
  • Docker konteynerni to'xtatganda yuboradigan signal —
Yechim
  • 408 Request Timeout.
  • closeIdleConnections — ishlayotgan so'rovlarga tegmaydi.
  • 1000 — keepAliveTimeoutBuffer; 1 s → 2 s da yopiladi.
  • katta — ulanishni mijoz (LB) birinchi yopsin.
  • SIGTERM — terminaldagi Ctrl+C esa SIGINT.

2-mashq (o'rta): sozlama tekshiruvchisi

timeoutTekshir(sozlama, lbBoshVaqt) toza funksiyasini yozing. U ogohlantirishlar massivini qaytarsin: keepAliveTimeout ≤ lbBoshVaqt → "502 xavfi"; headersTimeout > requestTimeout → "headersTimeout requestTimeout dan katta"; headersTimeout > 30 000 → "Slowloris'ga sekin javob". Hammasi joyida bo'lsa — bo'sh massiv. Sukut qiymatlar (5 000, 60 000, 300 000) va kanon qiymatlari (65 000, 10 000, 30 000) bilan sinang; LB — 60 000. Saytda ham ishlaydi.

Yechim
js
function timeoutTekshir(s, lbBoshVaqt) {
  const xatolar = [];
  if (s.keepAliveTimeout <= lbBoshVaqt) xatolar.push("502 xavfi");
  if (s.headersTimeout > s.requestTimeout) {
    xatolar.push("headersTimeout requestTimeout dan katta");
  }
  if (s.headersTimeout > 30_000) {
    xatolar.push("Slowloris'ga sekin javob");
  }
  return xatolar;
}

const sukut = {
  keepAliveTimeout: 5_000,
  headersTimeout: 60_000,
  requestTimeout: 300_000,
};
const kanon = {
  keepAliveTimeout: 65_000,
  headersTimeout: 10_000,
  requestTimeout: 30_000,
};
console.log(timeoutTekshir(sukut, 60_000));
console.log(timeoutTekshir(kanon, 60_000));

Konsolda:

text
[ '502 xavfi', "Slowloris'ga sekin javob" ]
[]

Sukut sozlamalar proxy ortida ikki muammo beradi. Kanon qiymatlari — toza.

3-mashq (qiyin): majburiy uzish

«yop() funksiyasi» bo'limidagi yop ni oling va ikkinchi kanon testini Node'da takrorlang: ishlovchi hech qachon javob bermaydi, yop(server, 200) chaqiriladi. Natija false bo'lsin, mijozdagi fetch esa TypeError bilan tugasin. Ikkalasini va o'tgan vaqtni (100 ms gacha yaxlitlab) chiqaring.

Yechim
js
import { createServer } from "node:http";

function yop(server, kutishMs) {
  return new Promise((resolve) => {
    const iv = setInterval(() => server.closeIdleConnections(), 100);
    const majburiy = setTimeout(() => {
      server.closeAllConnections();
      tugat(false);
    }, kutishMs);
    function tugat(ozVaqtida) {
      clearInterval(iv);
      clearTimeout(majburiy);
      resolve(ozVaqtida);
    }
    server.close(() => tugat(true));
    server.closeIdleConnections();
  });
}

const server = createServer(() => {}); // hech qachon javob bermaydi
await new Promise((ok) => server.listen(0, "127.0.0.1", ok));
const osilgan = fetch(`http://127.0.0.1:${server.address().port}`)
  .catch((x) => x);
await new Promise((ok) => setTimeout(ok, 50));

const t0 = performance.now();
console.log("o'z vaqtida:", await yop(server, 200));
const ms = Math.round((performance.now() - t0) / 100) * 100;
console.log("o'tdi:", ms, "ms");
const x = await osilgan;
console.log(x.name, x.message);

Konsolda:

text
o'z vaqtida: false
o'tdi: 200 ms
TypeError fetch failed

Ishlovchi javob bermaydi — ulanish "ishlayotgan", closeIdleConnections unga tegmaydi. 200 ms dan keyin closeAllConnections uni uzadi.

4-mashq: API qadami

src/ishga-tushir.ts, src/yopish.ts va signalli src/server.ts ni «API qadami» bo'limidagidek yozing; test/yopish.test.ts ga ikki testni qo'shing. npm run check — 7/7. Jonli sinang: npm start, keyin curl -i $A/vazifalar — Keep-Alive: timeout=65. Commit qiling.

Yechim

Kod — «API qadami» bo'limida va «yop() funksiyasi» bo'limida (TypeScript'da server: Server, kutishMs = 10_000, natija Promise<boolean>). Testlar — «yop() funksiyasi» bo'limidagi va 3-mashqdagi stsenariylar, assert.rejects(..., { code: "ECONNREFUSED" }) va assert.ok(t < 1500) bilan. Eski testlar ishgaTushir({ port: 0 }) orqali server oladi va oxirida yop(server, 2000) bilan yopadi.

10. Real ishda

  • Kubernetes, Docker — konteyner to'xtatilganda SIGTERM, keyin (sukut 30 s / 10 s) SIGKILL. Graceful shutdown shu oraliqqa sig'ishi kerak.
  • AWS ALB, GCP LB, nginx — har birining bo'sh ulanish vaqti bor; Node'niki undan katta. Bu xato haqida ko'p kompaniya blog yozgan.
  • Express, Fastify, NestJS — hammasi ostida o'sha node:http serveri; timeout'lar xuddi shunday beriladi (app.listen() qaytargan serverga yoki createServer ga).
  • Intervyu: "load balancer ortida 502 sababi?", "graceful shutdown qanday yoziladi?", "Slowloris nima?".

Xulosa

  • headersTimeout va requestTimeout — mijoz yuborishi uchun vaqt; 408 bilan yopiladi; Slowloris va sekin tanaga qarshi.
  • Node muddatni har connectionsCheckingInterval (30 s) da tekshiradi — 10 s timeout amalda 13 s chiqdi.
  • keepAliveTimeout + 1 s bufer (Node 24); proxy ortida LB'nikidan katta bo'lsin — vazifalar-api da 65 s.
  • server.close() keyin bo'shagan keep-alive'ni kutadi (7.4 s); closeIdleConnections() bilan 1.4 s; closeAllConnections() — oxirgi chora.
  • Signal → yop() → process.exitCode; process.exit() emas.

Keyingi dars: Reverse proxy orqasida Node — nginx va Caddy, X-Forwarded-For va soxta IP sarlavhasi xavfi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node.js server timeout'lari: keepAliveTimeout, headersTimeout, Slowloris va graceful shutdown — IlmHamroh