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

http.createServer: req va res bilan framework'siz Node server

Qisqacha: http.createServer((req, res) => { … }) — har so'rovda shu funksiya chaqiriladi. req (IncomingMessage) — kelgan so'rov: method, url, headers va o'qiladigan oqim sifatida tana. res (ServerResponse) — javob: statusCode, setHeader, writeHead, write, end. server.listen(port, host) portni tinglay boshlaydi. Qoida: har so'rov albatta res.end() bilan tugashi kerak, sarlavhalar esa tanadan oldin yuboriladi.

Bu darsda

  • http.createServer va listen bilan server yozib, uni fetch bilan o'zi tekshiradigan qilasiz.
  • req va res ning asosiy xususiyat va metodlarini ishlatasiz.
  • ERR_HTTP_HEADERS_SENT va "javob kelmayapti" xatolarining sababini tushuntirasiz.
  • Port, host (127.0.0.1, 0.0.0.0, ::) va EADDRINUSE ni ajratasiz.
  • vazifalar-api loyihasini ochasiz: GET /vazifalar ishlaydigan birinchi versiya.

Oldin bilishingiz kerak: HTTP server tomonda, Stream nima, EventEmitter asoslari, TypeScript'ni Node'da to'g'ridan ishga tushirish.

1. Nega bu kerak?

O'tgan darsda Node serveri qanday matn olishini ko'rdik. Endi o'zimiz server yozamiz. Express 5 ni bir necha darsdan keyin o'rnatamiz — lekin avval usiz. Sabab oddiy: Express'dagi req va res — aynan shu darsdagi obyektlar. Express ularga faqat qo'shimcha metodlar qo'shgan. res.json() ichida nima bor? Nega "Cannot set headers after they are sent" xatosi chiqadi, nega so'rov "osilib" qoladi — bularni tagidan bilsangiz, har qanday framework'da tez topasiz.

O'xshatish: oshxonadagi buyurtma oynasi. Ofitsiant varaqcha uzatadi (req): nima, nechta, qaysi stol. Oshpaz javob tayyorlaydi (res): avval patnisga yorliq qo'yadi (sarlavhalar), keyin taomni (tana) va oxirida qo'ng'iroqni chaladi (end). Qo'ng'iroq chalinmasa, ofitsiant kutaveradi. Yorliqni taom ketgandan keyin yopishtirib bo'lmaydi.

2. Birinchi server

2.1 Butun sikl bitta faylda

js
import http from "node:http";

const server = http.createServer((req, res) => {
  console.log("so'rov:", req.method, req.url);
  res.writeHead(200, { "Content-Type": "text/plain; charset=utf-8" });
  res.end("Assalomu alaykum!");
});
server.listen(0, "127.0.0.1", async () => {
  const { address, port } = server.address();
  console.log("tinglayapti:", address, port > 0);
  const res = await fetch(`http://127.0.0.1:${port}/salom`);
  console.log(res.status, res.headers.get("content-type"));
  console.log(await res.text());
  server.close(() => console.log("server yopildi"));
});

Konsolda:

text
tinglayapti: 127.0.0.1 true
so'rov: GET /salom
200 text/plain; charset=utf-8
Assalomu alaykum!
server yopildi

Qatorma-qator:

  • http.createServer(funksiya) — server obyektini yaratadi. Funksiya — so'rov ishlovchisi (request listener): har kelgan so'rovda chaqiriladi. Aslida server — EventEmitter, funksiya esa "request" hodisasining tinglovchisi.
  • server.listen(0, "127.0.0.1", callback) — portni ochadi. Callback — "tinglash boshlandi" paytida. Port 0 — OS bo'sh port tanlaydi (net darsi), haqiqiysi server.address().port da.
  • Shu jarayon ichida fetch bilan o'ziga so'rov yubordik: server va mijoz bitta dasturda. Bu — keyingi darslardagi barcha misollar va testlarning naqshi.
  • server.close(callback) — yangi ulanishlarni qabul qilishni to'xtatadi. Callback — hammasi yopilganda.

Oddiy dasturda esa server yopilmaydi: listen(3000) dan keyin jarayon to'xtamaydi, chunki event loop'da ochiq port bor (event loop darsi). Ctrl+C bilan to'xtatasiz.

2.2 Jarayon qanday ketadi

sequenceDiagram
  participant M as Mijoz
  participant N as Node (llhttp)
  participant K as Sizning funksiya
  M->>N: so'rov baytlari
  N->>K: req (sarlavhalar tayyor)
  K->>N: res.writeHead(200, ...)
  K->>N: res.end("...")
  N-->>M: holat, sarlavhalar, tana

Diagrammadagi muhim nuqta: funksiyangiz sarlavhalar kelishi bilan chaqiriladi. Tana bo'lsa, u hali yo'lda — req oqimidan keyin o'qiladi. Buni tana o'qish darsida qilamiz.

Xuddi shu yo'l jadvalda — bitta so'rovning hayoti qadamma-qadam:

Qadam Kim Natija
1 Node (llhttp) baytlardan so'rov qatori va sarlavhalar ajratiladi
2 Node req va res obyektlari yaratiladi, funksiyangiz chaqiriladi
3 sizning funksiya res.writeHead / setHeader — sarlavhalar tayyorlanadi
4 sizning funksiya res.end(...) — tana va "javob tugadi" belgisi
5 Node holat qatori, sarlavhalar va tana simga yoziladi; ulanish keep-alive bo'lib qoladi

3-qadamni 4-qadamdan keyin qilib bo'lmaydi — buni pastda xato bilan ko'ramiz.

3. req — IncomingMessage

req — http.IncomingMessage klassining obyekti. Eng ko'p ishlatiladiganlari:

Xususiyat Misol Izoh
req.method "GET" doim katta harf
req.url "/vazifalar?bajarildi=true" yo'l + query, domensiz
req.headers { host: "localhost" } nomlar kichik harfda
req.rawHeaders ["Host", "localhost"] asl ko'rinish, juft-juft
req.httpVersion "1.1"
req.socket.remoteAddress "127.0.0.1" mijoz IP'si

Bir xil sarlavha ikki marta kelsa nima bo'ladi? Biz xom so'rovda X-Teg: uy va x-teg: ish yubordik. Server req.headers["x-teg"] va req.rawHeaders ni chiqardi (biz ishga tushirdik):

text
uy, ish
[
  'Host',       'localhost',
  'X-Teg',      'uy',
  'x-teg',      'ish',
  'Connection', 'close'
]

req.headers takrorlarni vergul bilan birlashtirdi; rawHeaders esa hammasini asl harflari bilan saqladi. Ba'zi sarlavhalar birlashtirilmaydi: masalan, Host takrorlansa birinchisi qoladi, set-cookie esa massiv bo'ladi. Odatda req.headers yetadi.

req.url haqida ikki muhim gap. Birinchi — unda domen yo'q, faqat yo'l va query. Ikkinchi — u mijoz yozganicha keladi: /vazifalar/, /Vazifalar va /vazifalar?x=1 — uch xil satr. Ularni qanday ajratishni keyingi darsda URL bilan hal qilamiz.

req — o'qiladigan oqim (Readable). Tana shu oqimdan keladi. GET so'rovda tana yo'q, lekin oqim baribir bor — darhol tugaydi.

Tekshirib ko'ring: Mijoz Content-Type: application/json yubordi. req.headers["Content-Type"] nima qaytaradi?

Javob

undefined. Node sarlavha nomlarini kichik harfga o'giradi — to'g'risi req.headers["content-type"]. Bu eng ko'p uchraydigan boshlovchi xatosi.

4. res — ServerResponse

4.1 Holat va sarlavhalar

Javobni ikki uslubda qurish mumkin:

  • Bosqichma-bosqich: res.statusCode = 201, res.setHeader("X-Kurs", "IlmHamroh"), keyin res.end(...).
  • Bir zarbada: res.writeHead(201, { "Content-Type": "…" }).

Ikkalasini aralashtirsa-chi? Biz sinadik: server avval statusCode = 201, ikki setHeader qildi, keyin writeHead(200, "Hammasi joyida", {...}), end("tayyor") va oxirida yana setHeader ga urindi:

js
import http from "node:http";

const server = http.createServer((req, res) => {
  res.statusCode = 201;
  res.setHeader("X-Kurs", "IlmHamroh");
  res.setHeader("Content-Type", "text/plain");
  console.log(res.headersSent, res.getHeaderNames());
  res.writeHead(200, "Hammasi joyida", {
    "Content-Type": "text/plain; charset=utf-8",
  });
  console.log(res.headersSent);
  res.end("tayyor");
  try {
    res.setHeader("X-Kech", "1");
  } catch (e) {
    console.log(`${e.code}: ${e.message}`);
  }
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const res = await fetch(`http://127.0.0.1:${server.address().port}/`);
console.log(res.status, res.statusText);
const h = res.headers;
console.log(h.get("x-kurs"), h.get("content-type"));
server.closeAllConnections();
server.close();

Konsolda:

text
false [ 'x-kurs', 'content-type' ]
true
ERR_HTTP_HEADERS_SENT: Cannot set headers after they are sent to the client
200 Hammasi joyida
IlmHamroh text/plain; charset=utf-8

Nimani ko'rdik:

  1. setHeader sarlavhalarni xotirada yig'adi, hali hech narsa yuborilmagan (headersSent — false).
  2. writeHead yig'ilganlarni o'zinikilar bilan birlashtiradi: bir xil nomda writeHead niki ustun (Content-Type), qolgani saqlanadi (X-Kurs). statusCode = 201 ham writeHead(200) bilan almashdi.
  3. writeHead dan keyin headersSent — true. Ikkinchi argument — holat matni (status message). Odatda berilmaydi, Node OK, Not Found kabi standart matnni qo'yadi.
  4. Yuborilgandan keyin sarlavha qo'shib bo'lmaydi: ERR_HTTP_HEADERS_SENT — "Mijozga yuborilgandan keyin sarlavhalarni o'rnatib bo'lmaydi". Sarlavhalar tanadan oldin simdan ketgan — qaytarib bo'lmaydi.

Bu xato real loyihada ko'pincha shunday chiqadi: funksiya bir shartda javob yuboradi, lekin return unutilgan, keyingi qator yana javob yubormoqchi bo'ladi. Express'da ham aynan shu xabar.

4.2 Tana: write va end

  • res.write(bo'lak) — tananing bir qismi. Birinchi write sarlavhalarni ham yuboradi.
  • res.end(oxirgi?) — "javob tugadi". Ixtiyoriy oxirgi bo'lak bilan.

O'tgan darsdan eslang: faqat res.end(matn) bo'lsa, Node Content-Length qo'yadi; write bo'lsa — chunked. res — yoziladigan oqim (Writable), shuning uchun faylni pipeline(fayl, res) bilan to'g'ridan javobga ulash mumkin — buni static fayl darsida qilamiz.

4.3 end unutilsa

js
import http from "node:http";

const server = http.createServer((req, res) => {
  if (req.url === "/unutdi") return; // ❌ res.end() yo'q
  res.end("ok");
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const base = `http://127.0.0.1:${server.address().port}`;

console.log(await (await fetch(base + "/")).text());
try {
  await fetch(base + "/unutdi", { signal: AbortSignal.timeout(300) });
} catch (e) {
  console.log(e.name, e.message);
}
server.closeAllConnections();
server.close();

Konsolda:

text
ok
TimeoutError The operation was aborted due to timeout

Server xato bermadi, log ham yo'q — so'rov shunchaki "osilib" qoldi. Mijoz bizning 300 ms chegaramiz bilan uzdi (AbortSignal.timeout — fetch darsi). Chegara bo'lmaganda brauzer daqiqalab kutardi. Serverda esa har osilgan so'rov ulanishni band qiladi. Shuning uchun har shoxchada (if/else, try/catch) javob tugashini tekshiring. Server o'zi qachon voz kechishini (requestTimeout) timeout'lar darsida sozlaymiz.

4.4 Javob yasash — toza funksiya sifatida

vazifalar-api hamma javobni JSON qiladi. Bu takrorlanadigan ish: JSON.stringify, Content-Type, baytlardagi uzunlik. Uni bitta yordamchi funksiyaga yig'amiz. Avval mantiqni serversiz ko'ramiz — bu blok saytda jonli ishlaydi:

js
function buildJson(status, data, extra = {}) {
  const body = JSON.stringify(data);
  const headers = {
    "Content-Type": "application/json; charset=utf-8",
    "Content-Length": new TextEncoder().encode(body).length,
    ...extra,
  };
  return { status, headers, body };
}

const answer = buildJson(201, { matn: "Non olish ✓" }, {
  Location: "/vazifalar/1",
});
console.log(answer.headers["Content-Length"]); // 24
console.log(answer.body);

Konsolda:

text
24
{"matn":"Non olish ✓"}

body 22 belgi, lekin 24 bayt (✓ — 3 bayt). ...extra — qo'shimcha sarlavhalar sukutdagilar ustiga yoziladi (spread). Loyihadagi json(res, …) aynan shu, faqat oxirida res.writeHead va res.end chaqiradi va baytlarni Buffer.byteLength sanaydi.

5. listen: port va host

5.1 Host — kim ulana oladi

listen Kim ulana oladi
listen(3000, "127.0.0.1") faqat shu kompyuter (IPv4)
listen(3000, "0.0.0.0") tarmoqdagi hamma (IPv4)
listen(3000) hamma; IPv4 ham, IPv6 ham (::)

Host berilmasa nima bo'lishini tekshirdik — server.address():

text
{ address: '::', family: 'IPv6', port: 59440 }

:: — IPv6 dagi "hamma manzillar". Node uni ikki stekli qilib ochadi: IPv4 ulanishlar ham qabul qilinadi. Ya'ni host'siz listen = server butun tarmoqqa ochiq.

5.2 Port band bo'lsa

Bitta portni ikki dastur tinglay olmaydi. Biz vazifalar-api ni 4210-portda ishga tushirib, ikkinchi nusxasini ham o'sha portda ochdik (biz ishga tushirdik, qisqartirilgan):

text
node:events:505
    throw er; // Unhandled 'error' event
    ^

Error: listen EADDRINUSE: address already in use 127.0.0.1:4210

"Manzil allaqachon ishlatilmoqda". Ko'pincha sababi — oldingi terminalda yopilmay qolgan server. Unhandled 'error' event — server ham EventEmitter va "error" tinglovchisi yo'q edi (EventEmitter asoslari). Port band bo'lsa jarayon yiqilishi to'g'ri: noto'g'ri portda "ishlayotgandek" turgan server yomonroq.

Windows'da portni kim band qilganini topish: netstat -ano | findstr :4210 — oxirgi ustun PID. Linux/macOS'da lsof -i :4210.

5.3 Port qayerdan keladi

Qattiq yozilgan 3000 o'rniga muhit o'zgaruvchisi: Number(process.env.PORT ?? 3000) (process darsi). Hosting platformalari portni o'zi beradi, testlar 0 ishlatadi. .env va config'ni env darsida tartibga solamiz.

Tekshirib ko'ring: Server listen(3000) bilan ochilgan. Kafedagi Wi-Fi'da o'tirgan boshqa odam unga ulana oladimi?

Javob

Ha, agar firewall to'smasa. Host berilmagan — server :: da, ya'ni hamma tarmoq interfeyslarida tinglaydi. Faqat o'zingiz uchun bo'lsa "127.0.0.1" bering. Keyingi bo'lim — aynan shu haqida.

6. Hujumchi nigohi: ochiq qolgan dev server

Ishlab chiqish paytida serverda hali himoya yo'q: auth ham, hajm cheklovi ham. Uni listen(3000) bilan ochib, kafeda yoki yotoqxona Wi-Fi'sida o'tirsangiz, shu tarmoqdagi istalgan odam http://sizning-IP:3000/vazifalar ni ochadi. Tarmoqdagi ochiq portlarni avtomatik qidiradigan dasturlar bor — ular bir necha soniyada topadi. Ma'lumotlaringizni o'qishi, o'chirishi yoki serverga katta so'rovlar yuborib yiqitishi mumkin.

Himoya:

  • Ishlab chiqishda doim "127.0.0.1". vazifalar-api da host — process.env.HOST ?? "127.0.0.1": sukut bo'yicha yopiq, ochish uchun ataylab o'zgaruvchi berasiz.
  • Telefoningizdan sinash kerak bo'lsa — vaqtincha HOST=0.0.0.0 va faqat uy tarmog'ida.
  • Konteyner ichida 0.0.0.0 kerak bo'ladi — u yerda tashqi himoyani Docker tarmog'i beradi. Buni deploy qismlarida ko'ramiz.

7. API qadami: vazifalar-api boshlanadi

7.1 Loyiha papkasi

vazifalar frontend'i yonida yangi papka oching (masalan C:/Users/…/vazifalar-api) va git init qiling. tsconfig.json va eslint.config.js — 24-qismdagi valyuta dan nusxa (NodeNext, strict, noUncheckedIndexedAccess, exactOptionalPropertyTypes, erasableSyntaxOnly, strictTypeChecked). package.json (kanon):

json
{
  "name": "vazifalar-api",
  "version": "0.1.0",
  "private": true,
  "description": "vazifalar ilovasining backend'i — REST API",
  "license": "MIT",
  "type": "module",
  "engines": { "node": ">=24" },
  "scripts": {
    "start": "node src/server.ts",
    "test": "node --test \"test/**/*.test.ts\"",
    "tip": "tsc --noEmit",
    "lint": "eslint . --max-warnings=0",
    "check": "npm run tip && npm run lint && npm test"
  },
  "devDependencies": {
    "@eslint/js": "10.0.1",
    "@types/node": "24.19.2",
    "eslint": "10.13.0",
    "typescript": "6.0.3",
    "typescript-eslint": "8.71.2"
  }
}

Muhimlari: "start": "node src/server.ts" — build yo'q, Node .ts ni type stripping bilan bajaradi (24-qism va'dasi — server ham shu sxemada). Prod bog'liqlik — 0: hozircha faqat Node. @types/node 24.x — Node 24 ga mos turlar.

Tuzilma:

text
vazifalar-api/
  src/
    server.ts   — kirish nuqtasi: createServer + listen
    ilova.ts    — so'rov ishlovchisi (routing shu yerda)
    ombor.ts    — vazifalar xotirada
    vazifa.ts   — Vazifa turi va teglar
  test/
    yordamchi.ts, api.test.ts

Nega server.ts va ilova.ts alohida? ilova.ts faqat (req, res) => … funksiyasini yasaydi va portni bilmaydi. Testlar uni listen(0) bilan, server.ts esa haqiqiy portda ishga tushiradi. Bitta ilova — ikki xil ishga tushirish.

7.2 Model va ombor

src/vazifa.ts — Vazifa turi. Teglarni (#uy, #ish) server o'zi matndan ajratadi — 22-qism frontend'idagi qoida bilan bir xil regex. Bugun faylda faqat tur va teglariniOl (kanon kodi; tekshiruv funksiyalari tana darsida qo'shiladi):

ts
export type Vazifa = {
  id: number;
  matn: string;
  bajarildi: boolean;
  /** matndagi #teglar — server o'zi ajratadi */
  teglar: string[];
  /** ISO 8601, UTC (Z bilan) */
  yaratildi: string;
  yangilandi: string;
};

export const MAKS_MATN = 200;

const TEG = /(?<![\p{L}\p{N}_#])#[\p{L}\p{N}_]+(?:['‘’][\p{L}\p{N}_]+)*/gu;

export function teglariniOl(matn: string): string[] {
  return [...new Set((matn.match(TEG) ?? [])
    .map((t) => t.slice(1).toLowerCase()))];
}

export type YangiVazifa = { matn: string; bajarildi?: boolean };
export type VazifaOzgarishi = { matn?: string; bajarildi?: boolean };

MAKS_MATN = 200 — holat 05 qiymati. 26-qism bazasiga moslash uchun keyinroq 100 ga tushadi — buni o'sha darsda ko'rasiz.

src/ombor.ts — vazifalar xotirada (kanon kodi; telefonda o'qish uchun uzun qatorlar bo'lingan, ma'nosi o'zgarmagan):

ts
// ombor.ts — vazifalar xotirada (v0)
import { teglariniOl } from "./vazifa.ts";
import type {
  Vazifa, VazifaOzgarishi, YangiVazifa,
} from "./vazifa.ts";

export class XotiraOmbori {
  readonly #vazifalar = new Map<number, Vazifa>();
  #keyingiId = 1;
  readonly #soat: () => Date;

  constructor(soat: () => Date = () => new Date()) {
    this.#soat = soat;
  }

  royxat(): Vazifa[] {
    return [...this.#vazifalar.values()]
      .map((v) => structuredClone(v));
  }

  ol(id: number): Vazifa | undefined {
    const v = this.#vazifalar.get(id);
    return v && structuredClone(v);
  }

  yarat(yangi: YangiVazifa): Vazifa {
    const hozir = this.#soat().toISOString();
    const v: Vazifa = {
      id: this.#keyingiId++,
      matn: yangi.matn,
      bajarildi: yangi.bajarildi ?? false,
      teglar: teglariniOl(yangi.matn),
      yaratildi: hozir,
      yangilandi: hozir,
    };
    this.#vazifalar.set(v.id, v);
    return structuredClone(v);
  }

  yangila(id: number, ozgarish: VazifaOzgarishi): Vazifa | undefined {
    const v = this.#vazifalar.get(id);
    if (!v) return undefined;
    if (ozgarish.matn !== undefined) {
      v.matn = ozgarish.matn;
      v.teglar = teglariniOl(ozgarish.matn);
    }
    if (ozgarish.bajarildi !== undefined) {
      v.bajarildi = ozgarish.bajarildi;
    }
    v.yangilandi = this.#soat().toISOString();
    return structuredClone(v);
  }

  ochir(id: number): boolean {
    return this.#vazifalar.delete(id);
  }
}

Ikki nozik joy. Har metod structuredClone bilan nusxa qaytaradi: tashqaridagi kod omborni sezdirmay o'zgartira olmaydi (chuqur nusxa). Konstruktor esa soat funksiyasini oladi — testda vaqtni boshqarish uchun.

7.3 Ilova va server

src/ilova.ts — bugun bitta marshrut. json — yuqoridagi buildJson ning loyihadagi shakli:

ts
// ilova.ts — framework'siz so'rov ishlovchisi (birinchi qadam)
import type { IncomingMessage, ServerResponse } from "node:http";
import type { XotiraOmbori } from "./ombor.ts";

type Sozlama = { ombor: XotiraOmbori; publicPapka: string };

function json(res: ServerResponse, holat: number, malumot: unknown,
  sarlavhalar: Record<string, string> = {}): void {
  const tana = JSON.stringify(malumot);
  res.writeHead(holat, {
    "Content-Type": "application/json; charset=utf-8",
    "Content-Length": Buffer.byteLength(tana),
    ...sarlavhalar,
  });
  res.end(tana);
}

export function ilovaYarat({ ombor }: Sozlama) {
  return (req: IncomingMessage, res: ServerResponse): void => {
    if (req.method === "GET" && req.url === "/vazifalar") {
      json(res, 200, ombor.royxat());
      return;
    }
    json(res, 404, { xato: `${req.url ?? "/"} topilmadi` });
  };
}

Xato javobi — { "xato": "…" }: 22-qismdagi frontend api.ts aynan shu shaklni o'qiydi. publicPapka — static fayllar uchun, static fayl darsida ishlatiladi.

src/server.ts (kanon, o'zgarmaydi):

ts
// server.ts — kirish nuqtasi: http.createServer + listen
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import { join } from "node:path";
import { ilovaYarat } from "./ilova.ts";
import { XotiraOmbori } from "./ombor.ts";

const port = Number(process.env.PORT ?? 3000);
const host = process.env.HOST ?? "127.0.0.1";

const server = createServer(ilovaYarat({
  ombor: new XotiraOmbori(),
  publicPapka: join(import.meta.dirname, "..", "public"),
}));
server.listen(port, host, () => {
  // PORT=0 — OS bo'sh port beradi; haqiqiysi address() da
  const { port: haqiqiy } = server.address() as AddressInfo;
  console.log(`vazifalar-api: http://${host}:${String(haqiqiy)}`);
});

server.address() turi string | AddressInfo | null — TCP serverda doim AddressInfo, shuning uchun as. String(haqiqiy) — strictTypeChecked shablon ichida sonni ochiq aylantirishni talab qiladi.

7.4 Ishga tushiramiz

bash
npm install
PORT=4210 npm start

Ikkinchi terminalda (biz ishga tushirdik, Date qatori olib tashlangan). Chiqishda ikki javobning birinchi qatoriga — status kodga qarang:

bash
curl -i http://127.0.0.1:4210/vazifalar
curl -i -X POST http://127.0.0.1:4210/vazifalar
text
vazifalar-api: http://127.0.0.1:4210

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 2
Connection: keep-alive
Keep-Alive: timeout=5

[]

HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
Content-Length: 31
Connection: keep-alive
Keep-Alive: timeout=5

{"xato":"/vazifalar topilmadi"}

Birinchisi to'g'ri: ombor bo'sh — []. Ikkinchisi esa yolg'on: /vazifalar bor, faqat POST hali yo'q. To'g'ri javob — 405 va qaysi metodlar borligi. Bu — keyingi darsning boshlanish nuqtasi. (PowerShell'da PORT=4210 npm start ishlamaydi: $env:PORT=4210; npm start.)

7.5 Birinchi testlar

test/yordamchi.ts — ilovani tasodifiy portda ochadi (kanon kodi):

ts
import { createServer } from "node:http";
import type { AddressInfo } from "node:net";
import { join } from "node:path";
import { ilovaYarat } from "../src/ilova.ts";
import { XotiraOmbori } from "../src/ombor.ts";

export async function ishgaTushir(): Promise<{
  asos: string;
  yop: () => Promise<void>;
}> {
  const server = createServer(ilovaYarat({
    ombor: new XotiraOmbori(),
    publicPapka: join(import.meta.dirname, "..", "public"),
  }));
  await new Promise<void>((r) => server.listen(0, "127.0.0.1", r));
  const { port } = server.address() as AddressInfo;
  return {
    asos: `http://127.0.0.1:${String(port)}`,
    yop: () =>
      new Promise<void>((r) => {
        server.closeAllConnections();
        server.close(() => { r(); });
      }),
  };
}

export function jsonYubor(metod: string, tana: unknown): RequestInit {
  return {
    method: metod,
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(tana),
  };
}

yop da closeAllConnections() shart: aks holda keep-alive ulanishlari testni 5–6 soniya ushlab turadi. jsonYubor — POST/PATCH uchun, tana darsida kerak bo'ladi. test/api.test.ts boshida before/after bilan ishgaTushir() va yop() chaqiriladi (node:test), keyin ikkita test:

ts
test("GET /vazifalar — [], boshqa yo'l — 404", async () => {
  const r = await fetch(`${asos}/vazifalar`);
  assert.equal(r.status, 200);
  const tur = r.headers.get("content-type") ?? "";
  assert.match(tur, /application\/json/);
  assert.deepEqual(await r.json(), []);
  assert.equal((await fetch(`${asos}/yoq`)).status, 404);
});

test("teglariniOl: frontend qoidasi", () => {
  assert.deepEqual(teglariniOl("Non#uy ##x #o'quv #g‘isht #UY #uy"),
    ["o'quv", "g‘isht", "uy"]);
});

npm run check (biz ishga tushirdik, qisqartirilgan): tsc 0 xato, eslint 0, testlar pass 2, fail 0. Ikkinchi test kanondagi bilan bir xil; birinchisi bugungi qadam uchun — keyingi darslarda kanondagi to'liq CRUD va xato testlari bilan almashadi.

Hali commit qilmang — holat 05 static fayl darsida yakunlanadi va bitta commit bo'ladi.

8. Ko'p uchraydigan xatolar

Belgi Sabab Tuzatish
ERR_HTTP_HEADERS_SENT javob ikki marta yuborildi, return yo'q har javobdan keyin return
so'rov osilib qoladi biror shoxchada res.end() yo'q har yo'lda javobni tugating
EADDRINUSE port band, eski server ishlayapti eski jarayonni to'xtating yoki boshqa port
req.headers["Content-Type"] — undefined nomlar kichik harfda "content-type"
telefondan ochilmaydi 127.0.0.1 da tinglayapti vaqtincha HOST=0.0.0.0
test tugamaydi keep-alive ulanish ochiq closeAllConnections() keyin close()

9. Mashqlar

Mashqlar kurs/mashqlar/25/02-createserver/ da.

1-mashq (oson): bo'shliqlarni to'ldiring

  • Har so'rovda chaqiriladigan funksiya serverning "" hodisasi tinglovchisi.
  • Sarlavhalar yuborilganmi — res..
  • OS bo'sh port tanlasin: listen().
  • Faqat shu kompyuterdan ulanish: host "".
Yechim
  • request — createServer(fn) aslida server.on("request", fn).
  • headersSent — writeHead yoki birinchi write dan keyin true.
  • 0 — haqiqiy port server.address().port da.
  • 127.0.0.1 — loopback; 0.0.0.0 yoki host'siz — tarmoqqa ochiq.

2-mashq (o'rta): matn yoki JSON

buildJson ga o'xshash buildText(status, text) yozing: Content-Type: text/plain; charset=utf-8 va baytlardagi Content-Length. Sinang: buildText(404, "Topilmadi ✗") — holat, uzunlik va tanani chiqaring.

Yechim
js
function buildText(status, text) {
  return {
    status,
    headers: {
      "Content-Type": "text/plain; charset=utf-8",
      "Content-Length": new TextEncoder().encode(text).length,
    },
    body: text,
  };
}

const a = buildText(404, "Topilmadi ✗");
console.log(a.status, a.headers["Content-Length"], a.body);

Konsolda:

text
404 13 Topilmadi ✗

11 belgi, 13 bayt: ✗ — 3 bayt.

3-mashq (qiyin): har so'rovni log qilish

Server yozing: har javob tugaganda konsolga METOD yo'l → holat (ms) chiqarsin. Ishora: res.on("finish", …) — javob to'liq yuborilganda chaqiriladi; holat — res.statusCode. Vaqt barqaror bo'lmagani uchun ms o'rniga faqat ok deb chiqaring. fetch bilan GET /vazifalar (200) va GET /yoq (404) yuboring.

Yechim
js
import http from "node:http";

const server = http.createServer((req, res) => {
  res.on("finish", () => {
    console.log(`${req.method} ${req.url} → ${res.statusCode}`);
  });
  if (req.url === "/vazifalar") {
    res.end("[]");
    return;
  }
  res.statusCode = 404;
  res.end("topilmadi");
});
await new Promise((r) => server.listen(0, "127.0.0.1", r));
const base = `http://127.0.0.1:${server.address().port}`;
for (const path of ["/vazifalar", "/yoq"]) {
  await (await fetch(base + path)).text();
}
server.closeAllConnections();
server.close();

Konsolda:

text
GET /vazifalar → 200
GET /yoq → 404

"finish" — res Writable oqim hodisasi (Stream): oxirgi bayt OS'ga topshirildi. Log yozuvchi middleware'lar (logging darsi) aynan shu hodisani tinglaydi. Bu yerda return muhim: usiz ikkinchi res.end ham chaqirilardi.

4-mashq: API qadami

«API qadami» bo'limidagidek vazifalar-api ni oching: package.json, tsconfig.json, eslint.config.js, src/ ning to'rt fayli, test/ ning ikki fayli. npm run check toza bo'lsin, PORT=4210 npm start va ikki curl natijasi darsdagidek chiqsin. Commit qilmang.

Yechim

Kutilgan: npm run check — tip 0, lint 0, pass 2. curl -i …/vazifalar → 200 va []; curl -i -X POST …/vazifalar → 404 (hozircha). EADDRINUSE chiqsa — oldingi serverni Ctrl+C bilan to'xtating. Fayllar matni — «API qadami» bo'limida.

10. Real ishda

  • Har framework ostida shu — Express app ham (req, res) => … funksiyasi: http.createServer(app) bilan ishga tushadi. NestJS, Fastify, Next.js ning Node serveri ham oxir-oqibat http.Server.
  • Debug — serverni node --inspect src/server.ts bilan ochib, VS Code'da so'rov ishlovchisiga breakpoint qo'yish (debug darsi).
  • Uzoq yashaydigan jarayon — CLI bir necha soniyada tugaydi, server haftalab ishlaydi. Har so'rovda o'sib boradigan Map — xotira sizishi.
  • Intervyuda: "setHeader va writeHead farqi?", "Cannot set headers after they are sent — sababi?", "0.0.0.0 va 127.0.0.1 farqi?".

Xulosa

  • http.createServer(fn) — fn(req, res) har so'rovda; listen(port, host, cb) — tinglash.
  • req: method, url (domensiz), headers (kichik harf), rawHeaders; o'zi — Readable oqim.
  • res: statusCode/setHeader yig'adi, writeHead yuboradi; sarlavhalar ketgach — ERR_HTTP_HEADERS_SENT.
  • Har yo'l res.end() bilan tugaydi, aks holda so'rov osilib qoladi.
  • Host: 127.0.0.1 — faqat siz; host'siz — ::, butun tarmoq. Port band — EADDRINUSE.
  • vazifalar-api ochildi: GET /vazifalar → [], testlar 2/2; commit — holat 05 da.

Keyingi dars: Qo'lda routing va query parametrlar — URL bilan yo'l va query, /vazifalar/:id, 404 va 405 + Allow.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
http.createServer: req va res bilan framework'siz Node server — IlmHamroh