Mundarija (27)
- Bu darsda
- 1. Nega bu kerak?
- 2. Tana — oqim
- 2.1 Bo'laklarni yig'ish
- 2.2 Nega bo'lakni darhol satrga aylantirmaymiz
- 3. Tekshiruvlar tartibi
- 3.1 Content-Type va 415
- 3.2 Content-Length va 413
- 3.3 Chegara oshganda: throw yoki destroy?
- 3.4 JSON tahlili va 400
- 4. Ma'no tekshiruvi va 422
- 5. Formalar: application/x-www-form-urlencoded
- 6. Hujumchi nigohi: tana orqali hujumlar
- 7. API qadami: POST va PATCH
- 7.1 src/tana.ts — jsonTana
- 7.2 src/vazifa.ts — qo'lda tekshiruv
- 7.3 src/ilova.ts — ikki yangi shoxcha
- 7.4 Sinov
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): kod tanlang
- 2-mashq (o'rta): Content-Type ni ajratish
- 3-mashq (qiyin): urlencoded ham
- 4-mashq: API qadami
- 10. Real ishda
- Xulosa
- Manbalar
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/jsonemas bo'lsa 415;Content-Lengthchegaradan katta bo'lsa — o'qimasdan 413; o'qish paytida yig'indi chegaradan oshsa — yana 413. Keyin baytlarni bittaBufferga birlashtirib,JSON.parseqilasiz: 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-Lengthbo'yicha va o'qish paytida. - 400, 413, 415 va 422 ni ajratasiz.
- JSON va
application/x-www-form-urlencodedtanalarini o'qiysiz. vazifalar-apigaPOSTvaPATCHqo'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:
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:
100 200 olindi: 100 bayt
65536 413 juda kattaUch muhim nuqta:
total— baytlar yig'indisi (chunk.length—Bufferuzunligi baytda).- Chegaradan oshgan zahoti to'xtaymiz — qolgan 63 KB ni xotiraga yig'maymiz.
- Hammasi kelgach — bitta
Buffer.concat, keyin bir martatoString("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):
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:
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. BizningreadBodyqabul 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:
curl -i -X POST $A/vazifalar -d '{"matn":"Choy"}'Biz ishga tushirgan javob (Date, Connection qatorlarisiz):
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):
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:
const form = new URLSearchParams(
"matn=Non+olish+%23uy&bajarildi=true");
console.log(Object.fromEntries(form));Konsolda:
{ 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:
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:
[ '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):
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):
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:
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):
curl -i -X POST $A/vazifalar -H "Content-Type: application/json" \
-d '{"matn":"Non olish #uy #Bozor"}'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:
-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, POSTTestlar: 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/plainbilan 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
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:
"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
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:
200 {"matn":"Osh"}
200 {"matn":"Lag'mon","soni":"2"}
415Bu 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):
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 nomibody-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
stringifyqilsangiz, baytlar o'zgaradi va imzo mos kelmaydi. - Intervyuda: "400 va 422 farqi?", "413 ni qanday qo'yasiz?", "prototype pollution nima?".
Xulosa
req— oqim: bo'laklarnifor awaitbilan yig'ing,Buffer.concat, keyin bir martatoString("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 awaitichidagithrowdan keyin ham mijoz 413 oladi;req.destroy()—ECONNRESET. - Oq ro'yxat: noma'lum maydon — 422;
Object.assignbilan 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
- RFC 9110 — HTTP Semantics (2022-06): 400, 413 (Content Too Large), 415, 422 (Unprocessable Content), 201 va
Location: https://www.rfc-editor.org/rfc/rfc9110 - Node.js 24 hujjati — HTTP (
IncomingMessageoqim sifatida,req.resume): https://nodejs.org/docs/latest-v24.x/api/http.html · Stream (async iteratsiya): https://nodejs.org/docs/latest-v24.x/api/stream.html - MDN — Prototype pollution: https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/Prototype_pollution
- OWASP — Mass Assignment Cheat Sheet: https://cheatsheetseries.owasp.org/cheatsheets/Mass_Assignment_Cheat_Sheet.html
- Kanon:
vazifalar-apiholat 05 (tana.ts,vazifa.ts,ilova.ts, testlar),natijalar/tajriba/t04-tana.txt(throw / hodisalar / destroy; Node 24.21.0, 2026-10-24). Kichik misollar,JSON.parsexabarlari,curljavoblari vanpm run check— 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!