Mundarija (27)
- Bu darsda
- 1. Nega bu kerak?
- 2. Tanani o'qiydiganlar: json, urlencoded, raw
- 2.1 express.json({ limit })
- 2.2 express.urlencoded — HTML forma
- 2.3 express.raw — webhook uchun xom baytlar
- 3. Statik fayllar: express.static
- 4. Xavfsizlik sarlavhalari va CORS
- 4.1 helmet — 12 ta sarlavha bir qatorda
- 4.2 cors — boshqa origin'dan kelgan frontend
- 5. Cookie, log, siqish va limit
- 5.1 cookie-parser
- 5.2 morgan — so'rov logi
- 5.3 compression — siqish
- 5.4 express-rate-limit — so'rovlar chegarasi
- 6. Tartib muhim
- 7. API qadami: helmet va CORS (holat 17, 1-qism)
- 8. Hujumchi nigohi
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): to'ldiring
- 2-mashq (o'rta): imzo tekshiruvchi
- 3-mashq (qiyin): webhook marshruti
- 4-mashq: API qadami
- 11. Real ishda
- Xulosa
- Manbalar
Express middleware'lari: json, cors, helmet, cookie-parser va boshqalar
Qisqacha: Har Express ilovasida deyarli bir xil middleware'lar uchraydi. Tanani
express.json({ limit })o'qiydi va katta tanani 413 bilan to'xtatadi. Boshqa origin'dagi frontend'gacorsruxsat beradi,helmetesa 12 ta xavfsizlik sarlavhasini qo'yadi. Webhook imzosi uchunexpress.rawxom baytlarni saqlaydi. Har birining narxi bor: kanon o'lchovida siqish (compression) katta ro'yxatda serverni 45 % sekinlashtirdi. Shuning uchunvazifalar-apiga faqat helmet va cors kirdi.
Bu darsda
express.json,express.urlencoded,express.rawfarqini valimitnima uchun kerakligini bilasiz.corsni ruxsat ro'yxati bilan sozlaysiz va preflight javobini o'qiysiz.helmetqo'yadigan sarlavhalarni taniysiz.cookie-parser,morgan,compression,express-rate-limitqachon kerakligini o'lchov bilan ayta olasiz.- Webhook imzosini xom tana bilan to'g'ri tekshirasiz.
Oldin bilishingiz kerak: Middleware va request lifecycle, express.Router va qatlamli tuzilma, Request body o'qish va hajm cheklovi, crypto 1: hash va HMAC.
1. Nega bu kerak?
Middleware darsida middleware'ni o'zingiz yozdingiz. Bu — (req, res, next) funksiyasi: so'rovni ko'radi, nimadir qo'shadi va keyingisiga uzatadi. Qo'lda body o'qish darsida esa tanani bo'laklab yig'ish, hajmni sanash va JSON'ni tahlil qilish qancha kod talab qilishini ko'rdingiz.
Har loyihada shu ishlarni qayta yozmaslik uchun tayyor middleware'lar bor. Ular aeroportdagi nazorat punktlariga o'xshaydi. Biri yukni o'lchaydi (limit), biri pasportni tekshiradi (cors), biri hammaga ogohlantirish lentasi taqadi (helmet). Har punkt yo'lovchini biroz ushlab turadi — shuning uchun keraksiz punkt qo'yilmaydi.
Bu darsda o'nta mashhur middleware'ni ko'ramiz. Har biri uchun uch savol: nima qiladi, qanday sozlanadi, vazifalar-api ga kerakmi.
2. Tanani o'qiydiganlar: json, urlencoded, raw
2.1 express.json({ limit })
Siz uni Express'ga kirish darsidan beri ishlatyapsiz. Ichida — body-parser kutubxonasi: u tanani o'qiydi, hajmini sanaydi va JSON.parse qiladi. Asosiy sozlamalar:
| Sozlama | Sukut | Ma'nosi |
|---|---|---|
limit |
"100kb" |
Bundan katta tana — 413 xato |
type |
"application/json" |
Qaysi Content-Type larni o'qisin |
strict |
true |
Faqat obyekt va massiv (oddiy "salom" emas) |
Biz uchta so'rov yubordik va xato obyektining maydonlarini chiqardik:
json 120KB: 413 {"type":"entity.too.large","status":413,"expose":true,"message":"request entity too large"}
json buzuq: 400 {"type":"entity.parse.failed","status":400,"expose":true,"message":"Expected double-quoted property name in JSON at position 15 (line 1 column 16)"}
json text/plain: 200 {"olindi":0}Uch saboq:
- 120 KB tana sukut
100kbchegarasidan oshdi — 413 (Payload Too Large, "yuk juda katta"). Tana o'qilmadi, handler chaqirilmadi. LimitContent-Lengthbo'lmagan chunked tanada ham ishlaydi — baytlar o'qilgan sari sanaladi. Kanon testi ikkalasini ham tekshiradi ("413: Content-Length bilan va chunked"); bu chegarani qo'lda yozganingizni body o'qish darsida ko'rgansiz. - Oxirida ortiqcha vergul bor JSON — 400. Xabar: "15-pozitsiyada qo'shtirnoqli xususiyat nomi kutilgan edi". Xato obyektidagi
typemaydoni bo'yicha uni ajratib olasiz. text/plainbilan yuborilgan JSON — xato ham yo'q, tana ham yo'q. Express 5 da bunday so'rovdareq.body—undefined. Bu tuzoqni req obyekti darsida ko'rgandik.
vazifalar-api limitni kichraytiradi: express.json({ limit: "16kb" }). Bitta vazifa matni 200 belgigacha — 16 KB yetib ortadi. Kichik limit — hujumchi bir so'rov bilan server xotirasini to'ldira olmaydi.
Maslahat:
limitni ma'lumotingizga qarab tanlang. Rasm yuklash kerak bo'lsa, JSON limitini oshirmang — rasm uchun alohida marshrut va alohida middleware ishlatiladi.
2.2 express.urlencoded — HTML forma
Oddiy HTML <form> ma'lumotni application/x-www-form-urlencoded formatida yuboradi: ism=Ali&teg=uy. Uni express.urlencoded() o'qiydi. Asosiy sozlamasi extended:
forma extended:false: 200 {"ism":"Ali","teg":["uy","ish"],"manzil[shahar]":"Toshkent"}
forma extended:true: 200 {"ism":"Ali","teg":["uy","ish"],"manzil":{"shahar":"Toshkent"}}Bir xil tana: ism=Ali&teg=uy&teg=ish&manzil[shahar]=Toshkent. extended: false (Express 5 sukuti) kvadrat qavsni oddiy belgi deb biladi. extended: true esa qs kutubxonasi bilan ichma-ich obyekt yasaydi.
Ichma-ich obyekt qulay ko'rinadi, lekin xavfli ham. Hujumchi manzil[__proto__][x]=1 yoki filtr[$ne]= kabi kutilmagan shakl yuborishi mumkin. Kerak bo'lmasa — extended: false. vazifalar-api faqat JSON qabul qiladi, shuning uchun urlencoded umuman ulanmagan.
2.3 express.raw — webhook uchun xom baytlar
Webhook — boshqa xizmat sizning serveringizga o'zi so'rov yuborishi: "to'lov o'tdi", "buyurtma bekor qilindi". Click, Payme, Telegram Bot API shunday ishlaydi. Xizmat so'rovga imzo qo'shadi: tananing HMAC'i (crypto darsi). Siz imzoni o'zingiz hisoblab, solishtirasiz — shunda so'rov haqiqatan o'sha xizmatdan kelganini bilasiz.
Tuzoq shu yerda. Imzo aynan kelgan baytlardan hisoblangan. express.json() tanani obyektga aylantiradi — asl baytlar yo'qoladi. Obyektni qayta JSON.stringify qilsangiz, bo'shliqlar o'zgaradi:
webhook raw: 200 {"isBuffer":true,"ok":true}
webhook json: 200 {"qayta":"{\"summa\":50000,\"buyurtma\":17}","ok":false}Yuborilgan tana: {"summa": 50000, "buyurtma": 17} — ikki nuqtadan keyin bo'shliq bor. Qayta yig'ilgan satrda bo'shliq yo'q, demak HMAC boshqa, imzo "noto'g'ri" chiqdi. To'g'ri yo'l — webhook marshrutiga express.raw() qo'yish va uni express.json() dan oldin ulash:
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";
const app = express();
const SIR = process.env.WEBHOOK_SIR ?? "";
// 1) webhook — xom Buffer; global json() dan OLDIN
app.post(
"/webhook/tolov",
express.raw({ type: "application/json", limit: "64kb" }),
(req, res) => {
const kutilgan = createHmac("sha256", SIR)
.update(req.body) // Buffer — kelgan baytlar
.digest("hex");
const kelgan = req.get("x-imzo") ?? "";
const togri =
kelgan.length === kutilgan.length &&
timingSafeEqual(Buffer.from(kelgan), Buffer.from(kutilgan));
if (!togri) return res.sendStatus(401);
const tolov = JSON.parse(req.body.toString("utf8"));
res.json({ qabul: tolov.buyurtma });
},
);
// 2) qolgan hamma marshrutlar — oddiy JSON
app.use(express.json({ limit: "16kb" }));timingSafeEqual — solishtirish vaqti qaysi belgida farq borligini oshkor qilmasin (crypto darsi). Sir .env dan keladi, kodda yozilmaydi.
Imzo tekshiruvining o'zi — toza funksiya. Uni Web Crypto bilan brauzerda ham yozish mumkin. Quyidagi blokni «Ishga tushir» bilan sinang:
async function hmacHex(sir, matn) {
const enc = new TextEncoder();
const kalit = await crypto.subtle.importKey(
"raw", enc.encode(sir),
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"],
);
const imzo = await crypto.subtle.sign(
"HMAC", kalit, enc.encode(matn));
return [...new Uint8Array(imzo)]
.map((b) => b.toString(16).padStart(2, "0")).join("");
}
const SIR = "test-sir-faqat-namuna";
const xom = '{"summa": 50000, "buyurtma": 17}';
const kelganImzo = await hmacHex(SIR, xom); // xizmat yuborgan
const qayta = JSON.stringify(JSON.parse(xom));
console.log("xom tana: ", (await hmacHex(SIR, xom)) === kelganImzo);
const qaytaImzo = await hmacHex(SIR, qayta);
console.log("qayta yig'ilgan:", qaytaImzo === kelganImzo);
console.log(qayta);Konsolda:
xom tana: true
qayta yig'ilgan: false
{"summa":50000,"buyurtma":17}Tekshirib ko'ring: Webhook marshrutini
app.use(express.json())dan keyin ulasangiz,express.raw()nima oladi?
Javob
Hech narsa. express.json() tanani allaqachon o'qib bo'lgan — oqim (stream) bir marta o'qiladi (Stream darsi). express.raw() bo'sh oqimni ko'radi va req.body ni o'zgartirmaydi: u obyekt bo'lib qoladi, Buffer emas. HMAC'ga obyekt berilsa, xato chiqadi. Shuning uchun xom tana kerak bo'lgan marshrut global json() dan oldin turadi.
3. Statik fayllar: express.static
express.static(papka) — static fayl server darsida qo'lda yozgan kodingizning tayyor varianti (ichida serve-static). U ETag, Last-Modified, Range va 304 javobini o'zi beradi. Foydali sozlamalar:
| Sozlama | Sukut | Nima uchun |
|---|---|---|
maxAge |
0 |
Brauzer keshi (Cache-Control: max-age) |
index |
"index.html" |
Papka so'ralganda qaysi fayl |
dotfiles |
"ignore" |
.env kabi nuqtali fayllar berilmaydi |
fallthrough |
true |
Fayl yo'q bo'lsa next() — keyingi marshrutga |
vazifalar-api da public/index.html — API haqida qisqa sahifa. Kanon holat 13 da qo'lda yozilgan statik.ts ni o'chirib, express.static ga o'tgan edi. O'shanda bitta haqiqiy xato topilgan: Range: bytes=99999- so'roviga xato ishlovchimiz 500 qaytargan. Sabab — serve-static 416 statusli xato tashlaydi, ishlovchi esa uni tanimagan. Tuzatish — status < 500 && expose tarmog'i — middleware darsida xato ishlovchiga qo'shilgan va sizda allaqachon bor.
4. Xavfsizlik sarlavhalari va CORS
4.1 helmet — 12 ta sarlavha bir qatorda
helmet() javobga xavfsizlik sarlavhalarini qo'shadi. Kanon curl -i chiqishidan (holat 17), ularning bir qismi:
Content-Security-Policy: default-src 'self';base-uri 'self';…
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Referrer-Policy: no-referrer
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 0Eng muhimlari:
X-Content-Type-Options: nosniff— brauzerContent-Typeni "taxmin qilmasin". Aks holda JSON ichidagi<script>HTML sifatida ishlab ketishi mumkin.Strict-Transport-Security(HSTS) — brauzer bu saytga bir yil davomida faqat HTTPS orqali kirsin (HTTPS darsi).Content-Security-Policy(CSP) — sahifa qayerdan skript yuklay olishini cheklaydi. JSON API uchun kamroq muhim, lekinpublic/index.htmluchun foydali.X-XSS-Protection: 0— eski brauzer filtrini o'chiradi. Bu xato emas: o'sha filtrning o'zi zaiflik manbai bo'lgan.
Narxi: 1 paket, 0.1 MB, import 2 ms (kanon t14). Shuning uchun vazifalar-api ga kirdi.
Diqqat: helmet
Cross-Origin-Resource-Policy: same-originham qo'yadi. Bu sarlavha<img>va<script>kabino-corsso'rovlarga tegishli,fetchga emas. Agar API'dan rasm bersangiz va uni boshqa saytda<img>bilan ko'rsatmoqchi bo'lsangiz — rasm bloklanadi. Bunday holatdahelmet({ crossOriginResourcePolicy: { policy: "cross-origin" } }).
4.2 cors — boshqa origin'dan kelgan frontend
CORS mijoz tomondan darsida brauzerdagi qoidani ko'rgansiz: http://localhost:5173 dagi sahifa http://localhost:3000 dagi API'ga fetch qilsa, brauzer javobni JS'ga faqat server ruxsat bersa beradi. Ruxsat — javobdagi Access-Control-Allow-* sarlavhalari. Ularni cors middleware'i qo'yadi.
vazifalar frontend'i GitHub Pages'da, API esa boshqa manzilda bo'ladi. Shuning uchun CORS kerak. Kanon sozlamasi:
app.use(cors({
origin: [...corsOriginlar], // env'dan: CORS_ORIGINS
exposedHeaders: ["Location", "X-Request-Id"],
maxAge: 600, // preflight javobi 10 daqiqa keshlanadi
}));origin— ruxsat ro'yxati. Ro'yxatda bor origin kelsa, javobga aynan o'sha origin yoziladi.exposedHeaders— brauzer JS'ga sukut bo'yicha faqat bir nechta oddiy sarlavhani ko'rsatadi.LocationvaX-Request-Idni frontend o'qishi uchun ochiq e'lon qilamiz.maxAge— brauzer har PATCH oldidan preflight yubormasin.
Preflight — brauzer "oddiy bo'lmagan" so'rovdan (PATCH, Content-Type: application/json) oldin yuboradigan OPTIONS so'rovi: "Shu metod va sarlavhalar bilan so'rasam maylimi?". Kanon curl'i (sarlavhalar qisqartirilgan):
curl -i -X OPTIONS $A/vazifalar/1 -H 'Origin: http://localhost:5173' \
-H 'Access-Control-Request-Method: PATCH' \
-H 'Access-Control-Request-Headers: content-type'HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:5173
Vary: Origin, Access-Control-Request-Headers
Access-Control-Allow-Methods: GET,HEAD,PUT,PATCH,POST,DELETE
Access-Control-Allow-Headers: content-type
Access-Control-Max-Age: 600Begona origin (https://yomon.example) bilan oddiy GET esa 200 oladi — lekin Access-Control-Allow-Origin sarlavhasisiz. Diqqat qiling: server javob beradi, uni brauzer JS'ga bermaydi. CORS — server himoyasi emas, brauzer qoidasi. curl, Postman yoki boshqa server CORS'ga umuman qaramaydi.
sequenceDiagram
participant B as Brauzer (5173)
participant A as API
B->>A: OPTIONS /vazifalar/1<br/>Origin, Request-Method: PATCH
A-->>B: 204 + Allow-Origin, Allow-Methods
B->>A: PATCH /vazifalar/1 (JSON)
A-->>B: 200 + Allow-Origin
Note over B: JS javobni o'qiy oladiDiagrammada ikki bosqichga qarang: avval ruxsat so'raladi, keyin haqiqiy PATCH ketadi. maxAge: 600 bo'lsa, keyingi 10 daqiqada birinchi bosqich takrorlanmaydi.
Tekshirib ko'ring: Hamkasbingiz "CORS xatosi chiqyapti" deb
cors({ origin: "*" })qo'ydi. Nega bu yomon yechim bo'lishi mumkin va cookie bilan ishlaydigan API'da nima bo'ladi?
Javob
* — istalgan sayt brauzerdagi foydalanuvchi nomidan API javobini o'qiy oladi degani. Ochiq ma'lumot uchun bu normal, shaxsiy ma'lumot uchun — yo'q. Cookie bilan (credentials: true) brauzer * ni umuman qabul qilmaydi: CORS standarti credentials bilan aniq origin talab qiladi. Shuning uchun ba'zilar kelgan Origin ni "aks ettiradi" (origin: true) — bu esa har qanday saytga ruxsat beradi va eng xavfli variant. To'g'ri yo'l — ruxsat ro'yxati. Chuqur tafsilot — CORS chuqur darsida.
5. Cookie, log, siqish va limit
5.1 cookie-parser
Brauzer cookie'larni bitta Cookie: til=uz; savat=… sarlavhasida yuboradi. cookie-parser uni req.cookies obyektiga aylantiradi. Unga sir bersangiz, imzolangan cookie'larni ham tekshiradi (req.signedCookies). Biz sinadik (cookie-parser 1.4.7):
set-cookie: [
'til=uz; Max-Age=86400; Path=/; Expires=Sun, 25 Oct 2026 06:33:52 GMT; HttpOnly; SameSite=Lax',
'savat=s%3A3.tGTqDeH7qmjLM3jTHElHBrlPnjLohfggvHnK6z65xI4; Path=/; HttpOnly'
]
cookie qaytib: 200 {"cookies":{"til":"uz"},"signedCookies":{"savat":"3"}}
cookie soxta: 200 {"cookies":{"til":"uz"},"signedCookies":{"savat":false}}res.cookie("savat", "3", { signed: true }) qiymatga HMAC qo'shdi: s:3.<imzo>. Uchinchi so'rovda biz qiymatni 3 dan 9 ga o'zgartirdik — imzo mos kelmadi va cookie-parser false qaytardi.
Diqqat: imzo cookie'ni yashirmaydi. 3 hammaga ko'rinadi, faqat o'zgartirib bo'lmaydi. vazifalar-api hozir cookie ishlatmaydi. Cookie va sessiya auth qismida o'rganiladi, o'shanda kerak bo'ladi.
5.2 morgan — so'rov logi
morgan har so'rovga bitta qator yozadi. "dev" formatidagi haqiqiy qatorlar (biz ishga tushirdik):
POST /json 413 6.568 ms - 91
POST /json 400 9.095 ms - 148
POST /json 200 0.400 ms - 12Metod, yo'l, status, vaqt va javob hajmi. "combined" formati — nginx loglari bilan bir xil. morgan oddiy matn yozadi. Serverda esa JSON log va so'rov ID'si kerak. Shuning uchun vazifalar-api keyinroq Pino'ga o'tadi (Logging asoslari) — morgan kirmadi.
5.3 compression — siqish
compression() javobni gzip yoki Brotli bilan siqadi (zlib darsi). 1 KB dan kichik javobga tegmaydi (threshold). Biz sinadik: 17 baytli javob siqilmadi, 100 vazifali ro'yxat br bilan keldi. Kanon t14 sarlavhalariga qarang: siqilgan javobga content-encoding bilan birga transfer-encoding qo'shildi. Siqilgan hajm oldindan noma'lum, shuning uchun Content-Length o'rniga chunked encoding ishlatiladi.
Kanon o'lchovi qiziqroq. 100 vazifali ro'yxat (14 751 bayt) Brotli bilan 420 baytga tushdi — 35 marta kichik. Lekin server sekundiga kamroq javob bera boshladi:
- bo'sh express5 938 req/s
- helmet5 277 req/s
- cors5 689 req/s
- compression420 bayt, br3 148 req/s
- rate-limit4 883 req/s
- morgan5 230 req/s
- hammasi2 646 req/s
Manba: IlmHamroh kanon o'lchovi t14, 2026-10-24: express 5.3.0, Node 24.21.0, Windows 11, i5-12500H; autocannon 10 ulanish × 3 s, 2-tur; band mashina, server va yuk beruvchi bitta CPU'da
Grafik — ikkinchi tur. "compression" ustuniga qarang: /royxat bo'sh express'dan taxminan 45 % sekinroq (5 938 → 3 148 req/s). Siqish — CPU ishi, Node esa bitta asosiy thread'da ishlaydi (event loop darsi). Halol eslatma: birinchi turda farq atigi ~10 % edi (3 533 → 3 178). O'sha turda bo'sh express'ning o'zi past chiqdi. compression esa ikkala turda ham ~3 150–3 180 da turdi — uning "shifti" barqaror. Boshqa ustunlardagi farq — shovqin: ikki tur orasida bo'sh express 3 533 dan 5 938 gacha "sakradi".
Qaror: compression kirmadi. Siqishni Node'ning oldidagi reverse proxy (nginx) qiladi (reverse proxy darsi, to'liq — deploy qismida).
Kanondagi yana bir saboq: o'lchovning birinchi variantida yuk beruvchi (autocannon) server bilan bitta jarayonda edi. Raqamlar 3–10 marta past va ma'nosiz chiqdi (bo'sh express 5 094, helmet 1 337). Yuk beruvchi va server bitta event loop'ni bo'lishmasligi kerak.
5.4 express-rate-limit — so'rovlar chegarasi
Bir IP'dan daqiqasiga N tadan ortiq so'rovga 429 (Too Many Requests) qaytaradi. Biz limit: 2 bilan sinadik (express-rate-limit 8.7.1):
limit 1 200 x-ratelimit-limit: 2 | x-ratelimit-remaining: 1 | x-ratelimit-reset: 1792823704
limit 2 200 x-ratelimit-limit: 2 | x-ratelimit-remaining: 0 | x-ratelimit-reset: 1792823704
limit 3 429 retry-after: 60 | x-ratelimit-limit: 2 | x-ratelimit-remaining: 0 | x-ratelimit-reset: 1792823704Uchinchi so'rov 429 va Retry-After: 60 oldi — "60 soniyadan keyin urinib ko'ring". standardHeaders: "draft-8" bersangiz, IETF qoralamasidagi yangi RateLimit va RateLimit-Policy sarlavhalari chiqadi.
Nega kanonga kirmadi? Uch sabab:
- Hisob jarayon xotirasida. Ikki nusxa ishga tushsa, har biri o'z hisobini yuritadi. Umumiy hisob uchun Redis kerak (Redis qismi).
- Proxy orqasida hamma so'rov bitta IP'dan keladi —
trust proxysiz chegara noto'g'ri ishlaydi (reverse proxy darsi). - Haqiqiy ehtiyoj — login va parol tiklashni himoyalash. U auth qismida paydo bo'ladi.
Narxiga ham qarang: express-rate-limit 67 paket o'rnatadi. Lekin uning 65 tasi — express ning o'zi: npm 7+ "peer" bog'liqlikni avtomatik o'rnatadi. O'z bog'liqliklari faqat ikkita (debug, ip-address).
Tekshirib ko'ring: compression kichik javobni (17 bayt) nega siqmadi? Bu yaxshimi?
Javob
compression sukut bo'yicha 1 KB dan kichik javobni siqmaydi (threshold). Bu to'g'ri: kichik javobda siqish sarlavhalari va CPU vaqti tejalgan baytdan qimmatroq. Siqilgan 17 bayt hatto kattalashishi mumkin.
6. Tartib muhim
Middleware'lar ulanish tartibida ishlaydi (middleware darsi). Noto'g'ri tartib — jim xato. vazifalar-api dagi tartib (holat 17 oxirida; birinchi qator — sorovId — Express + TypeScript darsida qo'shiladi):
flowchart TD
A["sorovId — X-Request-Id"] --> B["helmet — sarlavhalar"]
B --> C["cors — preflight shu yerda tugaydi"]
C --> D["express.static — public/"]
D --> E["express.json — 16 KB"]
E --> F["/vazifalar routeri"]
F --> G["404"]
G --> H["xato ishlovchi"]Yuqoridan pastga o'qing va har qadamda "nega aynan shu yerda?" deb so'rang:
sorovIdbirinchi — xato bo'lsa ham javobda ID bo'lsin.helmetvacorserta — 404 va xato javoblarida ham sarlavhalar bo'lsin. Aks holda brauzer xato javobini o'qiy olmaydi va frontend "CORS xatosi" ko'radi, aslida esa 404 bo'lgan.corspreflightOPTIONSga o'zi 204 javob beradi — so'rov routerga yetmaydi.express.statictanani o'qishdan oldin — statik fayl uchun JSON tahlil qilish shart emas.- Webhook bo'lsa —
express.jsondan oldin.
7. API qadami: helmet va CORS (holat 17, 1-qism)
Holat 17 to'rt darsga bo'lingan: bugun middleware'lar, keyin Express 5 yangiliklari, shablonlar (faqat tajriba) va Express + TypeScript. Hali commit qilmang — commit to'rtinchi darsda.
O'rnatish (kanondagi aniq versiyalar):
npm i --save-exact helmet@8.3.0 cors@2.8.6
npm i -D --save-exact @types/cors@2.8.19helmet o'z tiplari bilan keladi, cors uchun alohida @types/cors. src/ilova.ts dagi o'zgarish (kanon, qisqartirilgan):
+import cors from "cors";
+import helmet from "helmet";
…
-export type Bogliqliklar = { ombor: VazifaOmbori };
+export type Bogliqliklar = {
+ ombor: VazifaOmbori;
+ /** bo'sh massiv: CORS sarlavhasi yo'q */
+ corsOriginlar?: readonly string[];
+};
…
app.disable("x-powered-by");
+ app.use(helmet());
+ app.use(cors({
+ origin: [...corsOriginlar],
+ exposedHeaders: ["Location", "X-Request-Id"],
+ maxAge: 600,
+ }));
app.use(express.static(join(import.meta.dirname, "..", "public")));Ro'yxat env'dan keladi — src/server.ts dagi o'zgarish (kanon, qatorlar bo'lingan):
import { yop } from "./yopish.ts";
+import { XotiraOmbori } from "./vazifalar/xotira-ombor.ts";
const { server, manzil } = await ishgaTushir({
port: Number(process.env.PORT ?? 3000),
host: process.env.HOST ?? "127.0.0.1",
+ bogliqliklar: {
+ ombor: new XotiraOmbori(),
+ // CORS_ORIGINS="http://localhost:5173,https://login.github.io"
+ corsOriginlar: (process.env.CORS_ORIGINS ?? "")
+ .split(",").map((o) => o.trim()).filter((o) => o !== ""),
+ },
});Testlar ham frontend origin'ini beradi — test/yordamchi.ts:
+import { XotiraOmbori } from "../src/vazifalar/xotira-ombor.ts";
+
+export const FRONTEND = "http://localhost:5173";
…
- const { server, manzil } = await ishga({ port: 0 });
+ const { server, manzil } = await ishga({
+ port: 0,
+ bogliqliklar: {
+ ombor: new XotiraOmbori(), corsOriginlar: [FRONTEND],
+ },
+ });Tipli config obyekti env validatsiyasi darsida keladi. Ilova corsOriginlar ni parametr sifatida oladi — testlar uni o'zi beradi, process.env ga tegmaydi.
Xato ishlovchi bugun o'zgarmaydi. serve-static (416, 403) va body-parser xatolari http-errors shaklida keladi: status va expose maydonlari bor (expose: true — xabarni mijozga ko'rsatsa bo'ladi). Ularni taniydigan status < 500 && expose tarmog'i middleware darsidan beri bor. Express + TypeScript darsida bu uzun shart httpErrorsmi degan type guard'ga ajratiladi — ma'nosi o'sha.
Yangi test fayli test/middleware.test.ts — preflight, ruxsat etilgan va begona origin, helmet (kanon, qatorlar bo'lingan):
import { after, before, test } from "node:test";
import assert from "node:assert/strict";
import { FRONTEND, ishgaTushir, xomSorov } from "./yordamchi.ts";
let asos = "";
let yop: () => Promise<boolean>;
before(async () => ({ asos, yop } = await ishgaTushir()));
after(() => yop());
test("CORS: ruxsat etilgan origin — preflight 204 va sarlavhalar; "
+ "begona origin — sarlavhasiz", async () => {
const pre = await xomSorov(`${asos}/vazifalar`, {
metod: "OPTIONS",
sarlavhalar: {
Origin: FRONTEND,
"Access-Control-Request-Method": "PATCH",
"Access-Control-Request-Headers": "content-type",
},
});
const s = pre.sarlavhalar;
assert.equal(pre.holat, 204);
assert.equal(s["access-control-allow-origin"], FRONTEND);
assert.match(String(s["access-control-allow-methods"]), /PATCH/);
assert.equal(s["access-control-max-age"], "600");
const oddiy = await xomSorov(`${asos}/vazifalar`, {
sarlavhalar: { Origin: FRONTEND },
});
assert.equal(
oddiy.sarlavhalar["access-control-allow-origin"], FRONTEND);
assert.equal(oddiy.sarlavhalar["access-control-expose-headers"],
"Location,X-Request-Id");
const begona = await xomSorov(`${asos}/vazifalar`, {
sarlavhalar: { Origin: "https://yomon.example" },
});
// server javob beradi — brauzer uni JS'ga bermaydi
assert.equal(begona.holat, 200);
assert.equal(
begona.sarlavhalar["access-control-allow-origin"], undefined);
});
test("helmet sarlavhalari va x-powered-by yo'q", async () => {
const r = await xomSorov(`${asos}/vazifalar`);
assert.equal(r.sarlavhalar["x-content-type-options"], "nosniff");
assert.ok(r.sarlavhalar["content-security-policy"]);
assert.equal(r.sarlavhalar["x-powered-by"], undefined);
});(Faqat format o'zgardi: uzun qatorlar bo'lingan, pre.sarlavhalar — s qisqa nomga olingan.)
Natija: prod bog'liqliklar 1 → 3 (daraxtda 65 → 68 paket). Bugungi kesim — holat 13 + helmet, cors va ikki test (CORS, helmet). Biz uni holat 17 nusxasidan keyingi darslar o'zgarishlarini olib tashlab tekshirdik (Node 24.21.0): tip 0, lint 0, testlar 9/9.
8. Hujumchi nigohi
| Hujum | Qanday | Himoya |
|---|---|---|
| Katta tana | 100 MB JSON yuborib xotirani to'ldirish | express.json({ limit: "16kb" }) → 413 |
| Soxta webhook | "To'lov o'tdi" so'rovini o'zi yuborish | HMAC imzo, xom tana, timingSafeEqual |
| Imzoni buzish | Qayta yig'ilgan JSON bilan tekshiruv "ishlamaydi" — dasturchi tekshiruvni o'chiradi | express.raw — tekshiruvni o'chirmaslik |
| CORS aks ettirish | Istalgan origin'ga ruxsat | Ruxsat ro'yxati |
| MIME taxmini | JSON ichidagi HTML'ni brauzer bajaradi | nosniff (helmet) |
| Ichma-ich forma | a[__proto__][x]=1 |
extended: false, oq ro'yxat |
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
req.body — undefined |
Content-Type JSON emas yoki express.json() ulanmagan |
Turini tekshiring (415) |
request entity too large |
Tana limit dan katta |
Limitni ataylab tanlang |
| Webhook imzosi doim noto'g'ri | express.json() oldin o'qigan |
express.raw() oldinroq |
| Frontend'da "CORS xatosi", aslida 404 | cors routerdan keyin ulangan |
cors ni erta ulang |
Location sarlavhasi JS'da null |
exposedHeaders da yo'q |
exposedHeaders: ["Location"] |
| Rasm boshqa saytda ko'rinmaydi | helmet CORP same-origin |
crossOriginResourcePolicy |
10. Mashqlar
Yechimlar kurs/mashqlar/25/14-middleware/ da.
1-mashq (oson): to'ldiring
express.json()sukut limiti —- Tana limitdan katta bo'lsa status —
- Webhook imzosini tekshirish uchun xom
Bufferberadigan middleware —express. - Preflight so'rovining HTTP metodi —
- JS'ga
Locationsarlavhasini ko'rsatish uchuncorssozlamasi —
Yechim
- 100kb —
body-parsersukuti. - 413 — Payload Too Large; xato obyektida
type: "entity.too.large". - raw —
express.raw({ type: "application/json" }), globaljson()dan oldin. - OPTIONS — brauzer o'zi yuboradi,
cors204 bilan javob beradi. - exposedHeaders — brauzer sukut bo'yicha faqat oddiy sarlavhalarni ko'rsatadi.
2-mashq (o'rta): imzo tekshiruvchi
Web Crypto bilan imzoTogrimi(sir, xomMatn, kelganHex) funksiyasini yozing. U true/false qaytarsin. kelganHex uzunligi 64 bo'lmasa yoki hex bo'lmasa — false, hisoblamasdan. Uch holatni sinang: to'g'ri imzo, qayta yig'ilgan JSON, buzuq satr. Toza JS — «Ishga tushir» bilan ishlaydi. Yordam: hmacHex — «Tanani o'qiydiganlar» bo'limida; satrni regex bilan tekshirish — RegExp darsi.
Yechim
async function hmacHex(sir, matn) {
const enc = new TextEncoder();
const kalit = await crypto.subtle.importKey(
"raw", enc.encode(sir),
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"],
);
const imzo = await crypto.subtle.sign(
"HMAC", kalit, enc.encode(matn));
return [...new Uint8Array(imzo)]
.map((b) => b.toString(16).padStart(2, "0")).join("");
}
async function imzoTogrimi(sir, xomMatn, kelganHex) {
if (!/^[0-9a-f]{64}$/.test(kelganHex)) return false;
return (await hmacHex(sir, xomMatn)) === kelganHex;
}
const SIR = "test-sir-faqat-namuna";
const xom = '{"summa": 50000, "buyurtma": 17}';
const imzo = await hmacHex(SIR, xom);
console.log(await imzoTogrimi(SIR, xom, imzo));
const qayta = JSON.stringify(JSON.parse(xom));
console.log(await imzoTogrimi(SIR, qayta, imzo));
console.log(await imzoTogrimi(SIR, xom, "salom"));Konsolda:
true
false
falseServerda solishtirishni timingSafeEqual bilan qiling. Web Crypto'da buning o'rniga crypto.subtle.verify("HMAC", kalit, imzoBaytlari, matn) bor — u ham vaqtga chidamli solishtiradi.
3-mashq (qiyin): webhook marshruti
Express ilovasini yozing: POST /webhook — express.raw + HMAC tekshiruvi (to'g'ri — 200 {"qabul":17}, noto'g'ri — 401), qolgan marshrutlar uchun express.json({ limit: "1kb" }) va POST /echo (tanani qaytaradi). Ilovani listen(0) bilan ishga tushirib, fetch bilan to'rt holatni tekshiring: to'g'ri imzo, noto'g'ri imzo, /echo kichik tana, /echo 2 KB tana (413). Oxirida close(). Express kerak — o'z loyihangizda ishga tushiring.
Yechim
import express from "express";
import { createHmac, timingSafeEqual } from "node:crypto";
const SIR = "test-sir-faqat-namuna";
const hmac = (b) => createHmac("sha256", SIR).update(b).digest("hex");
const app = express();
app.post("/webhook", express.raw({ type: "application/json" }),
(req, res) => {
const kelgan = Buffer.from(req.get("x-imzo") ?? "");
const kutilgan = Buffer.from(hmac(req.body));
const ok = kelgan.length === kutilgan.length &&
timingSafeEqual(kelgan, kutilgan);
if (!ok) return res.sendStatus(401);
res.json({ qabul: JSON.parse(req.body).buyurtma });
});
app.use(express.json({ limit: "1kb" }));
app.post("/echo", (req, res) => res.json(req.body));
app.use((err, _req, res, _next) => {
res.status(err.status ?? 500).json({ xato: err.type });
});
const server = app.listen(0, "127.0.0.1");
await new Promise((ok) => server.once("listening", ok));
const A = `http://127.0.0.1:${server.address().port}`;
const yubor = (yol, body, imzo = "") => fetch(A + yol, {
method: "POST", body,
headers: { "content-type": "application/json", "x-imzo": imzo },
});
const xom = '{"summa": 50000, "buyurtma": 17}';
for (const r of [
await yubor("/webhook", xom, hmac(xom)),
await yubor("/webhook", xom, hmac("boshqa")),
await yubor("/echo", '{"a":1}'),
await yubor("/echo", JSON.stringify({ m: "x".repeat(2048) })),
]) console.log(r.status, await r.text());
server.close();Konsolda (biz ishga tushirdik, express 5.3.0, Node 24.21.0):
200 {"qabul":17}
401 Unauthorized
200 {"a":1}
413 {"xato":"entity.too.large"}JSON.parse(req.body) — Buffer ni ham qabul qiladi (avval satrga aylantiradi).
4-mashq: API qadami
vazifalar-api ga helmet va cors ni qo'shing (aniq versiyalar), corsOriginlar ni Bogliqliklar ga va CORS_ORIGINS ni server.ts ga ulang. test/middleware.test.ts ga ikki test yozing: preflight (204, PATCH ruxsat, Max-Age: 600) va helmet (nosniff bor, x-powered-by yo'q). npm run check — yashil. Commit qilmang.
Yechim
Namuna — «API qadami» bo'limidagi diff va test. xomSorov — test/yordamchi.ts dagi node:http yordamchisi (holat 13 dan): shartli so'rovlar uchun fetch emas, xom so'rov kerak edi. Testda frontend origin'i FRONTEND konstantasi, ishgaTushir unga corsOriginlar: [FRONTEND] beradi. Testlar 7 → 9. Kanonning middleware.test.ts dagi uchinchi test (X-Request-Id) Express + TypeScript darsida qo'shiladi va soni 10 bo'ladi.
11. Real ishda
- Har Express loyihasida
express.json({ limit }),helmet,cors— standart uchlik. Intervyuda "cors nima qiladi va u server himoyasimi?" — mashhur savol. - To'lov tizimlari (Click, Payme, Stripe) va GitHub webhook'lari — xom tana va HMAC. Imzo tekshiruvi bo'lmagan webhook — "bepul to'lov" teshigi.
- Siqish va rate-limit ko'pincha nginx yoki bulut xizmatida (Cloudflare) qilinadi, Node'da emas.
- morgan eski loyihalarda ko'p uchraydi. Yangi loyihalarda Pino va pino-http.
Xulosa
express.json({ limit })— tana o'qish va hajm chegarasi; xatolartypemaydoni bilan (entity.too.large,entity.parse.failed).- Webhook imzosi uchun
express.raw()— globaljson()dan oldin; qayta yig'ilgan JSON imzoni buzadi. helmet— 12 sarlavha, 1 paket;cors— ruxsat ro'yxati,exposedHeaders, preflight 204.- CORS — brauzer qoidasi, server himoyasi emas: begona origin ham 200 oladi.
- Har middleware'ning narxi bor: compression
/royxatda −45 %; rate-limit xotirada bitta jarayon uchun.vazifalar-apiga faqat helmet va cors kirdi.
Keyingi dars: Express 5: async xatolar va yangi route sintaksisi — Express 4 dan nima o'zgardi va qaysi eski kod endi ishga tushmaydi.
Manbalar
- Express 5 hujjati —
express.json,express.raw,express.urlencoded,express.static: https://expressjs.com/en/5x/api.html (2026-10-24) - helmet 8.3.0, cors 2.8.6, compression 1.8.2, express-rate-limit 8.7.1, morgan 1.12.1, cookie-parser 1.4.7 — npm reestri, 2026-10-24.
- Fetch Standard — CORS protokoli: https://fetch.spec.whatwg.org/#http-cors-protocol
- IETF RateLimit sarlavhalari qoralamasi: https://datatracker.ietf.org/doc/draft-ietf-httpapi-ratelimit-headers/ (2026-10-24)
- Kanon:
vazifalar-apiholat 17 (helmet, cors, testlar),t14o'lchovi (hajm, import, yuk). json/urlencoded/raw/cookie/morgan/rate-limit chiqishlari — biz ishga tushirdik (express 5.3.0, Node 24.21.0, Windows 11).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!