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

Node.js request body o'qish: JSON tana, hajm cheklovi, 413 va 415

Qisqacha: req — oqim: tana bo'lak-bo'lak keladi va uni o'zingiz yig'asiz (for await (const bolak of req)). Yig'ishdan oldin uch tekshiruv: Content-Type — application/json emas bo'lsa 415; Content-Length chegaradan katta bo'lsa — o'qimasdan 413; o'qish paytida yig'indi chegaradan oshsa — yana 413. Keyin baytlarni bitta Buffer ga birlashtirib, JSON.parse qilasiz: xato bo'lsa 400. Ma'nosi noto'g'ri tana (bo'sh matn, noma'lum maydon) — 422.

Bu darsda

  • So'rov tanasini oqimdan to'g'ri yig'asiz va nega bo'laklarni satrga qo'shish xavfli ekanini ko'rasiz.
  • Hajm cheklovini ikki joyda qo'yasiz: Content-Length bo'yicha va o'qish paytida.
  • 400, 413, 415 va 422 ni ajratasiz.
  • JSON va application/x-www-form-urlencoded tanalarini o'qiysiz.
  • vazifalar-api ga POST va PATCH qo'shasiz — CRUD to'liq.

Oldin bilishingiz kerak: Qo'lda routing va query parametrlar, Async iteratorlar va Web Streams, Buffer va binar ma'lumot, JSON asoslari.

1. Nega bu kerak?

vazifalar-api hozircha faqat o'qiydi va o'chiradi. Yangi vazifa qo'shish uchun mijoz uni tanada yuboradi: {"matn":"Non olish #uy"}. Express'da bu req.body da tayyor turadi. node:http da esa req.body yo'q — tana hali simda, uni o'zingiz o'qishingiz kerak.

O'qish oddiy ko'rinadi, lekin bu backend'ning eng ko'p hujum qilinadigan joyi. Agar hajmni cheklamasangiz, kimdir 2 GB "JSON" yuboradi va server xotirasi tugaydi. Agar Content-Type'ni tekshirmasangiz, boshqa saytdagi forma sizning API'ingizga so'rov yuborishi osonlashadi. Agar JSON xatosini ushlamasangiz, bitta noto'g'ri qavs 500 beradi.

O'xshatish: pochtadagi posilka qabuli. Avval yorliqqa qaraysiz: "hujjat" deb yozilganmi (Content-Type)? Og'irligi ruxsat etilganidan oshmaydimi (Content-Length)? Yorliq yolg'on bo'lishi mumkin — shuning uchun qabul paytida tarozida ham tortasiz (o'qish paytidagi sanoq). Oxirida ochib, ichidagi hujjat to'g'ri to'ldirilganmi — tekshirasiz (JSON va maydonlar).

2. Tana — oqim

2.1 Bo'laklarni yig'ish

Stream darsidan eslang: req — o'qiladigan oqim, uni for await bilan o'qish mumkin (async iteratorlar). Har bo'lak — Buffer. Eng kichik to'g'ri o'quvchi, chegarasi bilan:

js
import http from "node:http";

const LIMIT = 1024;
async function readBody(req) {
  const chunks = [];
  let total = 0;
  for await (const chunk of req) {
    total += chunk.length;
    if (total > LIMIT) throw new Error("katta");
    chunks.push(chunk);
  }
  return Buffer.concat(chunks).toString("utf8");
}

const server = http.createServer(async (req, res) => {
  try {
    const text = await readBody(req);
    res.end(`olindi: ${Buffer.byteLength(text)} bayt`);
  } catch {
    res.writeHead(413).end("juda katta");
  }
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const url = `http://127.0.0.1:${server.address().port}/`;

for (const size of [100, 64 * 1024]) {
  const body = "x".repeat(size);
  const res = await fetch(url, { method: "POST", body });
  console.log(size, res.status, await res.text());
}
server.closeAllConnections();
server.close();

Konsolda:

text
100 200 olindi: 100 bayt
65536 413 juda katta

Uch muhim nuqta:

  • total — baytlar yig'indisi (chunk.length — Buffer uzunligi baytda).
  • Chegaradan oshgan zahoti to'xtaymiz — qolgan 63 KB ni xotiraga yig'maymiz.
  • Hammasi kelgach — bitta Buffer.concat, keyin bir marta toString("utf8").

2.2 Nega bo'lakni darhol satrga aylantirmaymiz

Ko'p qo'llanmalarda shunday kod uchraydi: body += chunk. U lotin harflarida ishlaydi, lekin boshqa belgilarda jim buziladi. Sababi: bitta belgi (masalan ✓ — 3 bayt) ikki bo'lak chegarasiga tushishi mumkin. Har bo'lakni alohida dekodlasak, belgining yarmi "buzilgan belgi" bo'ladi. Buni brauzerda ko'ring (blok saytda jonli ishlaydi):

js
const bytes = new TextEncoder().encode("ko'k choy ✓");
console.log(bytes.length); // 13
const parts = [bytes.slice(0, 11), bytes.slice(11)];

const decoder = new TextDecoder();
let wrong = "";
for (const p of parts) wrong += decoder.decode(p);
console.log(wrong);

const all = new Uint8Array(bytes.length);
let offset = 0;
for (const p of parts) {
  all.set(p, offset);
  offset += p.length;
}
console.log(decoder.decode(all));

Konsolda:

text
13
ko'k choy ���
ko'k choy ✓

✓ ning 3 baytidan biri birinchi bo'lakda, ikkitasi ikkinchisida qoldi. Alohida dekodlash — uchta �. Avval birlashtirish, keyin dekodlash — to'g'ri. Node'da Buffer.concat aynan shuni qiladi. Tarmoqda bo'lak chegarasini siz tanlamaysiz: kichik tana bitta bo'lakda keladi va xato ko'rinmaydi. Katta tana esa bir necha bo'lakka bo'linadi. Shuning uchun xato faqat "katta matnli vazifa"da, ishlab chiqarishda chiqadi.

Tekshirib ko'ring: Chegara 1024 bayt. Mijoz 1000 ta ✓ yubordi. Bizning readBody qabul qiladimi?

Javob

Yo'q — 413. 1000 belgi, lekin 3000 bayt. Chegara baytlarda, chunki xotirani baytlar egallaydi. "1000 belgigacha" degan qoida kerak bo'lsa — u alohida, ma'no tekshiruvida (pastda, 422).

3. Tekshiruvlar tartibi

Kanondagi jsonTana funksiyasi tanani o'qishdan oldin va o'qish paytida tekshiradi. Tartib muhim — eng arzon tekshiruv birinchi:

flowchart TD
  A["POST / PATCH"] --> B{"Content-Type<br/>application/json?"}
  B -- yo'q --> E1["415"]
  B -- ha --> C{"Content-Length<br/>> 16 KB?"}
  C -- ha --> E2["413 (o'qimasdan)"]
  C -- yo'q --> D{"o'qish paytida<br/>> 16 KB?"}
  D -- ha --> E3["413"]
  D -- yo'q --> F{"JSON.parse<br/>o'tdimi?"}
  F -- yo'q --> E4["400"]
  F -- ha --> G["maydonlar → 422 yoki davom"]

Diagrammaga qarang: 415 va birinchi 413 uchun tananing bir bayti ham o'qilmaydi — faqat sarlavhalar yetadi.

3.1 Content-Type va 415

Server faqat JSON kutadi. Content-Type boshqa bo'lsa — 415 Unsupported Media Type ("qo'llanmaydigan format"). Sarlavhada parametr bo'lishi mumkin (application/json; charset=utf-8), shuning uchun ; gacha olinadi va kichik harfga o'tkaziladi.

Bu tekshiruvning ikkinchi foydasi — xavfsizlik. Brauzerdagi oddiy HTML forma faqat uch turda yubora oladi (application/x-www-form-urlencoded, multipart/form-data, text/plain). JavaScript'siz application/json yuborib bo'lmaydi. Boshqa saytdan JSON yuborish uchun esa brauzer avval ruxsat so'raydi (CORS darsi). Ya'ni qat'iy application/json talabi begona saytdagi formadan keladigan soxta so'rovlarning katta qismini to'sadi. To'liq himoya — auth qismida (28).

Eng ko'p uchraydigan tuzoq — curl -d. Sarlavhasiz -d sukut bo'yicha application/x-www-form-urlencoded yuboradi:

bash
curl -i -X POST $A/vazifalar -d '{"matn":"Choy"}'

Biz ishga tushirgan javob (Date, Connection qatorlarisiz):

text
HTTP/1.1 415 Unsupported Media Type
Content-Type: application/json; charset=utf-8
Content-Length: 56

{"xato":"Content-Type: application/json bo'lishi kerak"}

Tuzatish: -H "Content-Type: application/json".

3.2 Content-Length va 413

Mijoz tana hajmini oldindan aytsa, chegaradan katta bo'lsa — o'qimasdan rad etamiz: 413 Content Too Large (eski nomi Payload Too Large; Node hali eskisini yozadi). Lekin Content-Length — mijozning da'vosi: yo'q bo'lishi mumkin (chunked) yoki yolg'on. Shuning uchun o'qish paytidagi sanoq ham shart. Ikkinchisi asosiy himoya, birinchisi — tejamkorlik.

Muhim detal: rad etganda ham req.resume() chaqiriladi — "tanani o'qib tashlab yubor". Usiz tana ulanishda qoladi va keep-alive ulanish keyingi so'rov uchun yaroqsiz bo'ladi.

3.3 Chegara oshganda: throw yoki destroy?

O'qish paytida chegara oshdi. Mijoz 413 ni ko'radimi? Kanon uch usulni sinadi (chegara 1 KB, 64 KB va 8 MB tana):

Usul Mijoz nimani oldi
for await ichida throw 413, ulanish keep-alive
on("data") hodisalari + reject 413, ulanish keep-alive
req.destroy() javob yo'q — ECONNRESET

Bu — kanonning haqiqiy topilmasi. Avval kodda "for await + throw ECONNRESET beradi" degan taxminiy izoh yozilgan edi. O'lchov uni rad etdi. Node 24 da throw dan keyin ham javob yetib boradi — 8 MB chunked tanada ham. req.destroy() esa soketni darhol uzadi — mijoz faqat "ulanish uzildi" xatosini ko'radi va nima bo'lganini bilmaydi. Kod soddaroq for await variantiga qaytarildi. Saboq: tarmoq xatti-harakati haqidagi izohni o'lchamasdan yozmang.

3.4 JSON tahlili va 400

Tana to'liq keldi. JSON.parse noto'g'ri matnda SyntaxError tashlaydi. Node 24 dagi haqiqiy xabarlar (biz ishga tushirdik):

text
SyntaxError: Expected property name or '}' in JSON at position 1 (line 1 column 2)
SyntaxError: Unexpected end of JSON input
SyntaxError: Expected double-quoted property name in JSON at position 7 (line 1 column 8)

Ular {matn: (kalit qo'shtirnoqsiz), bo'sh tana va {"a":1,} (oxirgi vergul) uchun. Tarjimasi: "xususiyat nomi yoki } kutilgan edi, 1-pozitsiyada", "JSON kutilmaganda tugadi", "qo'shtirnoqli xususiyat nomi kutilgan edi". Bu mijozning xatosi — 400 Bad Request. Ushlanmasa, xato markaziy ushlovchiga yetadi va 500 bo'ladi: server o'zini aybdor deb ko'rsatadi.

Xabarni mijozga to'liq qaytarish kerakmi? Kanon qisqa matn qaytaradi: "Tana yaroqli JSON emas". Pozitsiya foydali bo'lishi mumkin, lekin parser ichki tafsilotlarini ko'rsatmaslik xavfsizroq.

4. Ma'no tekshiruvi va 422

JSON to'g'ri, lekin ichi noto'g'ri bo'lishi mumkin: {"matn":" "}, {"matn":42}, {"matn":"Choy","admin":true}. Bunga 422 Unprocessable Content ("tushunarli, lekin bajarib bo'lmaydi"; Node holat matnida hali eski nomi — Unprocessable Entity): sintaksis to'g'ri, ma'no noto'g'ri. 400 va 422 chegarasi:

Tana Kod Sabab
{matn: 400 JSON emas
[1, 2] 422 JSON, lekin obyekt emas
{"matn":" "} 422 bo'sh matn
{"matn":"Choy","admin":true} 422 noma'lum maydon

Noma'lum maydon nega xato? Oq ro'yxat (allowlist) qoidasi: server faqat o'zi bilgan maydonlarni qabul qiladi. Bugun admin hech narsa qilmaydi. Ertaga kimdir Object.assign(vazifa, tana) yozsa — mijoz id ni yoki boshqa ichki maydonni o'zgartira oladi. Bunday hujumning nomi — mass assignment (ommaviy tayinlash). Qo'lda tekshiruvni Zod darsida sxema bilan almashtiramiz — g'oya o'sha.

5. Formalar: application/x-www-form-urlencoded

HTML forma sukut bo'yicha tanani query ko'rinishida yuboradi: matn=Non+olish+%23uy&bajarildi=true. Uni o'tgan darsdagi URLSearchParams o'qiydi — bu blok saytda ham ishlaydi:

js
const form = new URLSearchParams(
  "matn=Non+olish+%23uy&bajarildi=true");
console.log(Object.fromEntries(form));

Konsolda:

text
{ matn: 'Non olish #uy', bajarildi: 'true' }

E'tibor bering: bajarildi — satr "true", mantiqiy emas. Formada hamma narsa satr, turni o'zingiz aylantirasiz. vazifalar-api formani qabul qilmaydi (415) — u faqat JSON API. Fayl yuklash formalari (multipart/form-data) murakkabroq format — ular uchun alohida kutubxona kerak, buni fayllar mavzusida ko'ramiz.

6. Hujumchi nigohi: tana orqali hujumlar

Xotirani to'ldirish. Chegarasiz readBody ga 2 GB yuboring — server hammasini xotiraga yig'adi va yiqiladi. Bitta so'rov — butun xizmat to'xtadi. Bizda ikki qatlamli himoya: Content-Length va o'qish paytidagi sanoq, 16 KB. Bitta vazifa uchun bu ko'p (matn 200 belgigacha).

Sekin tana. Hujumchi tanani sekundiga 1 baytdan yuboradi. Chegara oshmaydi, lekin ulanish daqiqalab band. Himoya — vaqt chegarasi (requestTimeout), uni timeout'lar darsida sozlaymiz.

__proto__ kaliti. JSON.parse "__proto__" kalitini oddiy xususiyat qilib yaratadi — o'zi zararsiz. Xavf uni boshqa obyektga qo'shganda chiqadi. Bu blok saytda ham ishlaydi:

js
const evil = JSON.parse(
  '{"matn":"salom","__proto__":{"admin":true}}');
console.log(Object.keys(evil), evil.admin);
const merged = Object.assign({}, evil);
console.log(merged.admin, Object.keys(merged));
const spread = { ...evil };
console.log(spread.admin, Object.keys(spread));

Konsolda:

text
[ 'matn', '__proto__' ] undefined
true [ 'matn' ]
undefined [ 'matn', '__proto__' ]

Object.assign __proto__ ga tayinlash qildi — natijaning prototipi almashdi va merged.admin endi true. Spread esa oddiy xususiyat yaratdi. Agar kodda if (user.admin) bo'lsa — hujumchi admin bo'ldi. Bu prototype pollution (prototipni ifloslantirish) hujumining asosi. Bizning himoyamiz — oq ro'yxat: __proto__ noma'lum maydon, 422. Qoida: mijoz tanasini hech qachon Object.assign yoki chuqur birlashtirish bilan ichki obyektga qo'shmang — faqat ruxsat etilgan maydonlarni bittalab ko'chiring.

7. API qadami: POST va PATCH

7.1 src/tana.ts — jsonTana

O'tgan darsdagi HttpXato ostiga (kanon kodi; uzun qatorlar telefon uchun bo'lingan):

ts
import type { IncomingMessage } from "node:http";

// 16 KB — bitta vazifa uchun yetarli dan ortiq
export const SUKUT_LIMIT = 16 * 1024;

export async function jsonTana(
  req: IncomingMessage, limit = SUKUT_LIMIT,
): Promise<unknown> {
  const tur = (req.headers["content-type"] ?? "")
    .split(";")[0]?.trim().toLowerCase();
  if (tur !== "application/json") {
    req.resume(); // tanani o'qimasak ulanish osilib qoladi
    throw new HttpXato(415,
      "Content-Type: application/json bo'lishi kerak");
  }
  // Content-Length bo'lsa — tanani o'qimasdan oldin rad etamiz
  const uzunlik = Number(req.headers["content-length"] ?? NaN);
  if (uzunlik > limit) {
    req.resume();
    throw new HttpXato(413, `Tana ${String(limit)} baytdan katta`);
  }
  const bolaklar: Buffer[] = [];
  let jami = 0;
  // Limitdan oshsa throw — Node 24 da mijoz baribir 413 oladi
  for await (const bolak of req as AsyncIterable<Buffer>) {
    jami += bolak.length;
    // chunked so'rovda hajm faqat o'qish paytida ma'lum
    if (jami > limit) {
      throw new HttpXato(413, `Tana ${String(limit)} baytdan katta`);
    }
    bolaklar.push(bolak);
  }
  const matn = Buffer.concat(bolaklar).toString("utf8");
  try {
    return JSON.parse(matn) as unknown;
  } catch {
    throw new HttpXato(400, "Tana yaroqli JSON emas");
  }
}

Natija turi — unknown: tana hali tekshirilmagan, TypeScript uni hech narsa deb hisoblamasin. Number(undefined ?? NaN) → NaN, NaN > limit → false — sarlavha yo'q bo'lsa birinchi tekshiruv jim o'tadi va ikkinchisi ishlaydi.

7.2 src/vazifa.ts — qo'lda tekshiruv

Faylga tekshiruv funksiyalari qo'shiladi. Natija — "yoki qiymat, yoki xato" turi (ok maydoni bo'yicha ajraladigan union, TypeScript qismidan tanish naqsh):

ts
export type Tekshiruv<T> =
  | { ok: true; qiymat: T }
  | { ok: false; xato: string };

function obyektmi(x: unknown): x is Record<string, unknown> {
  return typeof x === "object" && x !== null && !Array.isArray(x);
}

function matnniTekshir(x: unknown): Tekshiruv<string> {
  if (typeof x !== "string") {
    return { ok: false, xato: "matn — satr bo'lishi kerak" };
  }
  const m = x.trim();
  if (m === "" || m.length > MAKS_MATN) {
    return {
      ok: false,
      xato: `matn 1–${String(MAKS_MATN)} belgi bo'lishi kerak`,
    };
  }
  return { ok: true, qiymat: m };
}

/** POST tanasi: matn majburiy, bajarildi ixtiyoriy; boshqasi — xato */
export function yangiVazifaniTekshir(
  tana: unknown,
): Tekshiruv<YangiVazifa> {
  if (!obyektmi(tana)) {
    return { ok: false, xato: "tana — JSON obyekt bo'lishi kerak" };
  }
  const ortiqcha = Object.keys(tana)
    .filter((k) => k !== "matn" && k !== "bajarildi");
  if (ortiqcha.length > 0) {
    return {
      ok: false,
      xato: `noma'lum maydon: ${ortiqcha.join(", ")}`,
    };
  }
  const matn = matnniTekshir(tana.matn);
  if (!matn.ok) return matn;
  if (tana.bajarildi !== undefined
    && typeof tana.bajarildi !== "boolean") {
    return { ok: false, xato: "bajarildi — true yoki false" };
  }
  return {
    ok: true,
    qiymat: tana.bajarildi === undefined
      ? { matn: matn.qiymat }
      : { matn: matn.qiymat, bajarildi: tana.bajarildi },
  };
}

ozgarishniTekshir (PATCH) — xuddi shu naqsh, farqi: ikkala maydon ixtiyoriy, lekin kamida bittasi bo'lishi shart ({} → 422 o'zgarish yo'q: matn yoki bajarildi). Oxirgi qiymat nega bunday murakkab? exactOptionalPropertyTypes yoqilgan: bajarildi: undefined yozib bo'lmaydi — maydon yo yo'q, yo boolean.

7.3 src/ilova.ts — ikki yangi shoxcha

/vazifalar ga GET dan keyin:

ts
if (metod === "POST") {
  const t = yangiVazifaniTekshir(await jsonTana(req));
  if (!t.ok) throw new HttpXato(422, t.xato);
  const v = ombor.yarat(t.qiymat);
  json(res, 201, v, { Location: `/vazifalar/${String(v.id)}` });
  return;
}
res.setHeader("Allow", "GET, POST");

/vazifalar/:id ga GET dan keyin PATCH (ozgarishniTekshir → ombor.yangila, topilmasa 404), Allow esa "GET, PATCH, DELETE". yonalt ustidagi eslint-disable izohi endi olib tashlanadi — await bor.

POST javobi — 201 Created va Location sarlavhasi: "yangi resurs shu manzilda". Status kodlarni alohida darsda chuqur ko'ramiz.

7.4 Sinov

Biz ishga tushirdik (Date, Connection, Keep-Alive qatorlarisiz):

bash
curl -i -X POST $A/vazifalar -H "Content-Type: application/json" \
  -d '{"matn":"Non olish #uy #Bozor"}'
text
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
Content-Length: 159
Location: /vazifalar/1

{"id":1,"matn":"Non olish #uy #Bozor","bajarildi":false,"teglar":["uy","bozor"],"yaratildi":"2026-10-24T14:12:23.023Z","yangilandi":"2026-10-24T14:12:23.023Z"}

Teglarni server o'zi ajratdi va kichik harfga o'tkazdi: #Bozor → "bozor". Sana — UTC (Z). Qolgan holatlarning javob tanalari. Har qatorda chapda — yuborilgan narsa, o'ngda — status kod va xato matni; ular «Tekshiruvlar tartibi» diagrammasidagi tartibga mos:

text
-d '{matn:'                       → 400 {"xato":"Tana yaroqli JSON emas"}
-d '{"matn":"   "}'               → 422 {"xato":"matn 1–200 belgi bo'lishi kerak"}
-d '{"matn":"Choy","admin":true}' → 422 {"xato":"noma'lum maydon: admin"}
--data-binary @katta.json (20 KB) → 413 {"xato":"Tana 16384 baytdan katta"}
PATCH /vazifalar/1 -d '{}'        → 422 {"xato":"o'zgarish yo'q: matn yoki bajarildi"}
PUT /vazifalar                    → 405 Allow: GET, POST

Testlar: birinchi testni kanondagi uchta test bilan almashtiring — CRUD, xatolar, 413 (to'liq matni — 4-mashq yechimida). npm run check (biz ishga tushirdik): tip 0, lint 0, pass 4, fail 0. Kanonda yana bitta — static fayllar testi — keyingi darsda qo'shiladi va holat 05 commit qilinadi. Hali commit qilmang.

8. Ko'p uchraydigan xatolar

Belgi Sabab Tuzatish
req.body — undefined node:http da body yo'q tanani oqimdan o'qing
katta matnda � body += chunk Buffer.concat, keyin toString
curl -d → 415 sukut urlencoded -H "Content-Type: application/json"
noto'g'ri JSON → 500 JSON.parse ushlanmagan try/catch → 400
keyingi so'rov osiladi rad etilgan tana o'qilmadi req.resume()
2 GB so'rov serverni yiqitdi chegara yo'q Content-Length + o'qishdagi sanoq

9. Mashqlar

Mashqlar kurs/mashqlar/25/04-tana/ da.

1-mashq (oson): kod tanlang

  • Content-Type: text/plain bilan POST —
  • {"matn":} —
  • {"matn":""} —
  • Content-Length: 50000 (chegara 16 KB) —
Yechim
  • 415 — format qo'llanmaydi.
  • 400 — JSON sintaksisi buzilgan.
  • 422 — JSON to'g'ri, matn bo'sh.
  • 413 — sarlavhaning o'zi yetadi, tana o'qilmaydi.

2-mashq (o'rta): Content-Type ni ajratish

mediaType(header) yozing: sarlavha qiymatidan ; gacha qismni olib, bo'shliqlarni kesib, kichik harfga o'tkazsin; sarlavha yo'q bo'lsa — "". Sinang: "Application/JSON; charset=utf-8", " application/json", undefined, "text/plain".

Yechim
js
function mediaType(header) {
  return (header ?? "").split(";")[0].trim().toLowerCase();
}

const samples = ["Application/JSON; charset=utf-8",
  " application/json", undefined, "text/plain"];
for (const h of samples) console.log(JSON.stringify(mediaType(h)));

Konsolda:

text
"application/json"
"application/json"
""
"text/plain"

split(";")[0] — massivning birinchi elementi doim bor (bo'sh satrda ham [""]). TypeScript'da noUncheckedIndexedAccess bilan u string | undefined bo'ladi — kanondagi ?.trim() shuning uchun.

3-mashq (qiyin): urlencoded ham

readBody misolini kengaytiring: server application/json ni JSON.parse bilan, application/x-www-form-urlencoded ni Object.fromEntries(new URLSearchParams(matn)) bilan o'qisin, boshqasiga 415. Javob — o'qilgan obyekt JSON'da. fetch bilan ikkala tur va text/plain ni yuboring.

Yechim
js
import http from "node:http";

async function readText(req) {
  const chunks = [];
  for await (const chunk of req) chunks.push(chunk);
  return Buffer.concat(chunks).toString("utf8");
}

const server = http.createServer(async (req, res) => {
  const type = (req.headers["content-type"] ?? "")
    .split(";")[0].trim().toLowerCase();
  const text = await readText(req);
  let data;
  if (type === "application/json") data = JSON.parse(text);
  else if (type === "application/x-www-form-urlencoded") {
    data = Object.fromEntries(new URLSearchParams(text));
  } else {
    res.writeHead(415).end();
    return;
  }
  res.end(JSON.stringify(data));
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const url = `http://127.0.0.1:${server.address().port}/`;

const bodies = [
  ["application/json", '{"matn":"Osh"}'],
  ["application/x-www-form-urlencoded", "matn=Lag%27mon&soni=2"],
  ["text/plain", "salom"],
];
for (const [type, body] of bodies) {
  const res = await fetch(url, {
    method: "POST", headers: { "Content-Type": type }, body,
  });
  console.log(res.status, await res.text());
}
server.closeAllConnections();
server.close();

Konsolda:

text
200 {"matn":"Osh"}
200 {"matn":"Lag'mon","soni":"2"}
415

Bu o'quv misoli: chegara va JSON.parse xatosi ushlanmagan. Haqiqiy kodda «API qadami» dagi jsonTana tartibi kerak. "soni":"2" — formada son ham satr. Express'da bu ikki o'quvchi — express.json() va express.urlencoded() middleware'lari (mashhur middleware'lar).

4-mashq: API qadami

src/tana.ts ga jsonTana, src/vazifa.ts ga tekshiruv funksiyalari, src/ilova.ts ga POST va PATCH qo'shing. Testlarni kanondagi uchtasi bilan almashtiring. npm run check toza bo'lsin; «Sinov» bo'limidagi curl lar xuddi shunday javob bersin. Commit qilmang.

Yechim

Kanon testlari (test/api.test.ts, teglariniOl testidan oldin; importlarga request (node:http) va jsonYubor qo'shiladi):

ts
test("CRUD: yaratish → o'qish → o'zgartirish → o'chirish",
  async () => {
  const r1 = await fetch(`${asos}/vazifalar`,
    jsonYubor("POST", { matn: "  Non olish #uy #Bozor " }));
  assert.equal(r1.status, 201);
  const v = (await r1.json()) as {
    id: number; matn: string; teglar: string[]; bajarildi: boolean;
  };
  const yol = `${asos}/vazifalar/${String(v.id)}`;
  assert.equal(r1.headers.get("location"), `/vazifalar/${String(v.id)}`);
  assert.equal(v.matn, "Non olish #uy #Bozor");
  assert.deepEqual(v.teglar, ["uy", "bozor"]);
  assert.equal(v.bajarildi, false);
  const r2 = await fetch(yol,
    jsonYubor("PATCH", { bajarildi: true }));
  assert.equal(r2.status, 200);
  const r3 = await fetch(`${asos}/vazifalar?bajarildi=true`);
  assert.equal(((await r3.json()) as unknown[]).length, 1);
  assert.equal((await fetch(yol, { method: "DELETE" })).status, 204);
  assert.equal((await fetch(yol)).status, 404);
});

test("413: Content-Length bilan va chunked katta tana", async () => {
  const katta = JSON.stringify({ matn: "x".repeat(20_000) });
  const r = await fetch(`${asos}/vazifalar`, { method: "POST",
    headers: { "Content-Type": "application/json" }, body: katta });
  assert.equal(r.status, 413);
  // chunked: Content-Length yo'q — limit o'qish paytida
  const holat = await new Promise<number>((resolve, reject) => {
    const so = request(`${asos}/vazifalar`, { method: "POST",
      headers: { "Content-Type": "application/json" } }, (res) => {
      res.resume();
      resolve(res.statusCode ?? 0);
    });
    so.on("error", reject);
    for (let i = 0; i < 20; i++) so.write("x".repeat(1024));
    so.end();
  });
  assert.equal(holat, 413);
});

Uchinchi test — «xatolar»: 400 ({matn:), 415 (text/plain), 422 (bo'sh matn va ortiqcha maydon), 405 va Allow: GET, POST, 404 (/yoq), 400 (?bajarildi=ha) — har biri «Sinov» bo'limidagi jadvaldagidek assert.equal bilan. Kutilgan natija: tip 0, lint 0, pass 4. http.request 413 testida write ning chunked bo'lishi uchun ishlatiladi — fetch satr tanaga Content-Length qo'yadi.

10. Real ishda

  • Express — express.json({ limit: "100kb" }): ichida aynan shu tartib (Content-Type, Content-Length, sanoq, parse); kutubxona nomi body-parser, sukut chegarasi 100 KB.
  • nginx — client_max_body_size (sukut 1 MB): ilovagacha yetmasdan 413. Ikki qatlam — odatiy amaliyot.
  • Fayl yuklash — tana xotiraga emas, to'g'ridan diskka yoki bulut xotirasiga oqim bilan yoziladi (pipeline).
  • Webhook'lar (Telegram, to'lov tizimlari) — imzoni tekshirish uchun xom tana kerak: avval parse qilib, keyin qayta stringify qilsangiz, baytlar o'zgaradi va imzo mos kelmaydi.
  • Intervyuda: "400 va 422 farqi?", "413 ni qanday qo'yasiz?", "prototype pollution nima?".

Xulosa

  • req — oqim: bo'laklarni for await bilan yig'ing, Buffer.concat, keyin bir marta toString("utf8").
  • Tartib: Content-Type (415) → Content-Length (413, o'qimasdan) → o'qishdagi sanoq (413) → JSON.parse (400) → maydonlar (422).
  • Chegara baytlarda; Content-Length — da'vo, sanoq — haqiqat.
  • Node 24 da for await ichidagi throw dan keyin ham mijoz 413 oladi; req.destroy() — ECONNRESET.
  • Oq ro'yxat: noma'lum maydon — 422; Object.assign bilan tana birlashtirilmaydi (__proto__).
  • vazifalar-api: CRUD to'liq, testlar 4/4; static fayllar va commit — keyingi darsda.

Keyingi dars: Static fayl server — public/ papka, MIME turlari, ETag va 304, Range va 206, traversal himoyasi; holat 05 commit.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node.js request body o'qish: JSON tana, hajm cheklovi, 413 va 415 — IlmHamroh