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

HTTP caching va shartli so'rovlar: Cache-Control, ETag, 304 va If-Match bilan 412

Qisqacha: Server javobga ETag (resurs versiyasining "barmoq izi") qo'yadi. Mijoz keyingi safar If-None-Match: "3" yuboradi; resurs o'zgarmagan bo'lsa server tanasiz 304 Not Modified qaytaradi — trafik tejaladi. Cache-Control kim va qancha saqlashini aytadi (private, no-cache — faqat brauzer, har safar tekshirsin). Yozishda esa teskari shart: If-Match: "3" — "faqat 3-versiya bo'lsa yoz", aks holda 412 — bu ikki kishi bir vaqtda tahrirlaganda bir o'zgarish jim yo'qolishining oldini oladi. Tuzoq: Node fetch i If-None-Match bilan o'zi Cache-Control: no-cache qo'shadi va Express 304 bermaydi.

Bu darsda

  • Cache-Control direktivalarini (private, public, no-cache, no-store, max-age, s-maxage, stale-while-revalidate) to'g'ri tanlaysiz.
  • Kuchli va kuchsiz ETag farqini bilasiz va Express'ning ETag'ini o'zingiz hisoblaysiz.
  • If-None-Match → 304 oqimini qurasiz va fetch tuzog'ini tushuntira olasiz.
  • If-Match → 412 bilan yo'qolgan yangilanish (lost update) muammosini hal qilasiz.
  • vazifalar-api ga versiya maydoni, ETag va If-Match qo'shib, holat 26 ni commit qilasiz.

Oldin bilishingiz kerak: HTTP sarlavhalari, MIME turlari, cookie va kesh, API versiyalash va deprecation, Status kodlarni to'g'ri tanlash, crypto: tasodif, hash va HMAC.

1. Nega bu kerak?

Ikki muammo, ikkalasining yechimi bitta — resurs versiyasi.

Birinchisi — trafik. Malika telefonda vazifalar ro'yxatini har ochganda 100 ta vazifa (16 KB) yuklanadi. Ro'yxat ertalabdan beri o'zgarmagan bo'lsa ham. Sekin mobil internetda bu sekundlar va pul. Yaxshiroq: "menda 3-versiya bor, o'zgarganmi?" deb so'rash va "yo'q" degan qisqa javob olish.

Ikkinchisi — yo'qolgan yangilanish. Ali va Vali bitta vazifani bir vaqtda ochdi. Ali matnni tahrirladi va saqladi. Vali (eski nusxada) "bajarildi" belgisini qo'ydi va saqladi. Valining so'rovi butun vazifani eski matn bilan yubordi — Alining o'zgarishi jim yo'qoldi. Hech kim xato ko'rmadi.

O'xshatish: kutubxona kitobi. Kitob ichida "nashr: 3" yozilgan. Siz kutubxonachidan "menda 3-nashr bor, yangisi chiqdimi?" deb so'raysiz — "yo'q" desa, kitobni qayta olmaysiz (304). Kitobga tuzatish kiritmoqchi bo'lsangiz — "men 3-nashrga tuzatish yozyapman" deysiz; agar 4-nashr allaqachon chiqqan bo'lsa, tuzatishingiz qabul qilinmaydi (412).

2. Cache-Control: kim, qancha saqlaydi

2.1 Keshlar zanjiri

02-qismda brauzer keshini ko'rgansiz. Server tomondan qaraganda javob bir nechta keshdan o'tishi mumkin:

Kesh Qayerda Kim uchun
brauzer keshi foydalanuvchi kompyuterida private — faqat shu foydalanuvchi
CDN, proxy (nginx proxy_cache) yo'lda shared — hamma foydalanuvchi

Agar Alining shaxsiy vazifalar ro'yxati umumiy keshga tushsa — Vali Alining vazifalarini ko'radi. Shuning uchun shaxsiy javob private.

2.2 Direktivalar

Direktiva Ma'nosi
max-age=60 60 soniya davomida yangi hisoblanadi — serverga so'ramasdan ishlatiladi
s-maxage=300 xuddi shu, lekin faqat umumiy keshlar uchun
no-cache saqlash mumkin, lekin har safar server bilan tekshirilsin (304 bo'lishi mumkin)
no-store umuman saqlanmasin (bank ko'chirmasi, parol sahifasi)
private / public faqat brauzer / umumiy kesh ham
stale-while-revalidate=30 muddati o'tgandan keyin 30 s eski nusxani ko'rsat, fonda yangila

Eng ko'p adashtiriladigan juftlik: no-cache "keshlama" degani emas. U "ishlatishdan oldin tekshir" degani. "Keshlama" — no-store.

2.3 API uchun tanlov

Javob Cache-Control Nega
GET /v1/vazifalar/:id private, no-cache shaxsiy; har safar tekshiriladi, o'zgarmagan bo'lsa 304
GET /v1/eksportlar/:id (holat) no-store holat har soniyada o'zgaradi — eski nusx foydasiz
public/ statik fayllar Express static sukuti (ETag + max-age=0) static darsi
ochiq, hamma uchun bir xil (valyuta kursi) public, max-age=60, stale-while-revalidate=30 CDN saqlaydi, server yuki kamayadi

Kanondagi keshYoq(res) funksiyasi hamma GET'ga private, no-cache qo'yadi. Nomi "kesh yo'q", lekin ma'nosi: "saqlash mumkin, faqat tekshirib ishlat".

Tekshirib ko'ring: Server foydalanuvchi profilini Cache-Control: max-age=3600 bilan qaytardi (private yo'q). Oradagi CDN uni saqladi. Qanday xavf bor?

Javob

private bo'lmasa, umumiy kesh javobni saqlashi mumkin. Keyingi foydalanuvchi o'sha URL'ni so'rasa (masalan /v1/men), CDN unga birinchi foydalanuvchining profilini bir soat davomida beradi. Shaxsiy javob — doim private (yoki no-store). Bu real hodisalarda ma'lumot sizishiga olib kelgan.

3. ETag va 304

3.1 ETag nima

ETag (entity tag) — resursning joriy holatini bildiruvchi qisqa satr, qo'shtirnoq ichida: "3". Resurs o'zgarsa — ETag ham o'zgaradi. Ikki xili bor:

Turi Ko'rinishi Ma'nosi
kuchli (strong) "3" bayt-baytigacha bir xil
kuchsiz (weak) W/"3f6d-uA4k…" ma'nosi bir xil (baytlari farq qilishi mumkin)

ETag'ni qanday yasash — sizning tanlovingiz. Ikki yo'l:

  1. Tana hash'idan — Express shunday qiladi: har JSON javobga W/"<uzunlik hex>-<SHA-1 base64>".
  2. Versiyadan — kanon bitta vazifa uchun shuni tanladi: "3". Hisoblash bepul, DB'da ham shu ustun bor.

3.2 Express ETag'ini o'zimiz hisoblaymiz

Express ETag'i sir emas — etag paketi tana uzunligi va SHA-1 hash'idan yasaydi. Quyidagi blok kanondagi haqiqiy 422 javobi tanasini oladi va uning ETag'ini qayta hisoblaydi (Web Crypto — brauzerda ham ishlaydi):

js
async function expressEtag(text) {
  const bytes = new TextEncoder().encode(text);
  const hash = await crypto.subtle.digest("SHA-1", bytes);
  const b64 = btoa(String.fromCharCode(...new Uint8Array(hash)));
  return `W/"${bytes.length.toString(16)}-${b64.slice(0, 27)}"`;
}

const body = JSON.stringify({
  type: "https://vazifalar-api.example/xatolar/validatsiya",
  title: "So'rov ma'lumoti noto'g'ri",
  status: 422,
  detail: "matn 1–200 belgi bo'lishi kerak",
  instance: "/vazifalar",
  kod: "validatsiya",
  xatolar: [
    { joy: "body", xabar: "matn 1–200 belgi bo'lishi kerak" },
  ],
  sorovId: "505f210a-1a64-47c8-8563-c59b99a02516",
});
console.log(await expressEtag(body));
console.log(await expressEtag('{"id":1}'));
console.log(await expressEtag('{"id":2}'));

Konsolda:

text
W/"13f-BgcBppSBLBwelZn5rnw1sjAPNKo"
W/"8-h5EdGu1QmHe4OkjsU292jNzSLfE"
W/"8-M1u4Sc28uxk+zyXJvSTJaEkyIGw"

Birinchi qator kanon curl chiqishidagi ETag: W/"13f-BgcBppSBLBwelZn5rnw1sjAPNKo" bilan aynan bir xil (holat 24). 13f — 319 bayt (hex), o'sha javobning Content-Length i. Tana bir belgiga o'zgarsa (1 → 2), hash butunlay boshqa.

TextEncoder — muhim: "–" (tire) 3 bayt. body.length (belgilar soni) bilan hisoblasangiz, uzunlik noto'g'ri chiqadi.

3.3 Shartli GET: If-None-Match → 304

Oqim — diagrammada:

sequenceDiagram
  participant M as Mijoz
  participant S as Server
  M->>S: GET /v1/vazifalar/1
  S-->>M: 200, ETag: "3", tana 156 bayt
  Note over M: nusxani va "3" ni saqlaydi
  M->>S: GET, If-None-Match: "3"
  alt o'zgarmagan
    S-->>M: 304 Not Modified, tana yo'q
  else o'zgargan
    S-->>M: 200, ETag: "4", yangi tana
  end

304 javobida tana yo'q — mijoz o'zidagi nusxani ishlatadi. Sarlavhalar (ETag, Cache-Control) esa bor.

Last-Modified + If-Modified-Since — eski, sanaga asoslangan juftlik. U soniya aniqligida ishlaydi: bir soniyada ikki o'zgarish bo'lsa, ikkinchisi ko'rinmaydi. Ikkalasi bo'lsa, server If-None-Match ni ustun qo'yadi (RFC 9110). Kanon ikkalasini ham qo'yadi.

3.4 Biz ishga tushirdik: fetch nima yuboradi

Kanon (holat 13) g'alati holatni topgan: fetch bilan If-None-Match yuborilsa, Express 304 bermaydi. Sababini ko'rish uchun server kelgan sarlavhalarni chop etadi. Blok Node uchun:

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

const task = { id: 1, matn: "Non olish #uy", versiya: 3 };
const etag = `"${task.versiya}"`;

const server = createServer((req, res) => {
  const inm = req.headers["if-none-match"] ?? "-";
  const cc = req.headers["cache-control"] ?? "-";
  console.log(`  server: If-None-Match=${inm}, Cache-Control=${cc}`);
  res.setHeader("ETag", etag);
  res.setHeader("Cache-Control", "private, no-cache");
  if (inm === etag) {
    res.statusCode = 304;
    return res.end();
  }
  res.setHeader("content-type", "application/json");
  res.end(JSON.stringify(task));
});
await new Promise((ok) => server.listen(0, "127.0.0.1", ok));
const { port } = server.address();
const url = `http://127.0.0.1:${port}/v1/vazifalar/1`;

const first = await fetch(url);
const size = (await first.text()).length;
const got = first.headers.get("etag");
console.log(`1) ${first.status}, ETag ${got}, ${size} bayt`);

const headers = { "If-None-Match": etag };
const second = await fetch(url, { headers });
const size2 = (await second.text()).length;
console.log(`2) ${second.status}, ${size2} bayt`);
server.closeAllConnections();
server.close();

Konsolda:

text
  server: If-None-Match=-, Cache-Control=-
1) 200, ETag "3", 43 bayt
  server: If-None-Match="3", Cache-Control=no-cache
2) 304, 0 bayt

Ikkinchi so'rovga biz qo'ymagan Cache-Control: no-cache sarlavhasi qo'shildi. Buni Node'ning fetch i (undici) Fetch standarti bo'yicha o'zi qiladi: so'rovda shartli sarlavha bo'lsa, oraliq keshlar "o'zidan javob bermasin". Bizning qo'lda yozilgan server bu sarlavhaga qaramaydi — 304 berdi.

Express esa fresh paketi orqali uni hurmat qiladi: "mijoz no-cache dedi — demak to'liq javob kerak". Kanon o'lchovi (t26, ?limit=100):

Mijoz Status Tana
node:http so'rovi, If-None-Match bilan 304 0 bayt
fetch, If-None-Match bilan 200 16 237 bayt

Shuning uchun kanon testlari shartli so'rovlarni node:http bilan yuboradi (xomSorov). curl ham 304 oladi. Brauzerdagi fetch qanday yo'l tutishi — sinalmagan (Chrome ishga tushirilmadi). Brauzerda odatda bu masala o'zi hal bo'ladi: brauzer If-None-Match ni o'zi yuboradi, 304 kelsa — JS ga keshdagi tanani 200 sifatida beradi.

3.5 304 nimani tejaydi

GET /v1/vazifalar?limit=100 javob tanasi
  • 200 (birinchi)16 237 bayt
  • 304 (o'zgarmagan)0 bayt

Manba: kanon t26-kesh, Node 24.21.0, i5-12500H, 2026-10-24

Tarmoq tejaldi. Lekin kanon yana bir narsani o'lchadi: 2 000 ketma-ket so'rovda 200 — 2.73 ms, 304 — 3.68 ms (loopback, band mashina, bitta o'lchov). Ro'yxatning ETag'i tana hash'idan — server baribir ro'yxatni yig'ib, JSON qilib, hash'laydi. Demak 304 server CPU'sini tejamaydi, faqat tarmoq va mijoz vaqtini. (304 ning sekinroq chiqishi — shovqin, sababi aniqlanmadi.) Bitta vazifada esa ETag versiyadan — u yerda server tanani yasamaydi ham.

Tekshirib ko'ring: Telefon 3G'da 16 KB ni 300 ms da yuklaydi. Server 304 ni 1 ms sekinroq qaytaradi. Kim yutadi?

Javob

Foydalanuvchi. 304 da tana yo'q — 300 ms yuklash o'rniga deyarli faqat tarmoq kechikishi qoladi. Serverdagi 1 ms farq bunga nisbatan sezilmaydi. Shuning uchun ETag sekin tarmoqli mobil mijozlar uchun eng foydali.

4. If-Match va 412: yo'qolgan yangilanish

4.1 Muammo kodda

Endi teskari yo'nalish — yozish. Quyidagi model brauzerda ishlaydi. Ali va Vali vazifani o'qiydi, keyin ikkalasi ham butun vazifani PUT qiladi:

js
function createStore() {
  const tasks = new Map([
    [1, { matn: "Non olish", bajarildi: false, versiya: 1 }],
  ]);
  return {
    get(id) {
      const t = tasks.get(id);
      return { etag: `"${t.versiya}"`, body: { ...t } };
    },
    put(id, next, ifMatch) {
      const t = tasks.get(id);
      if (ifMatch !== undefined && ifMatch !== `"${t.versiya}"`) {
        return { status: 412, etag: `"${t.versiya}"` };
      }
      Object.assign(t, next, { versiya: t.versiya + 1 });
      return { status: 200, etag: `"${t.versiya}"` };
    },
  };
}

function scenario(useIfMatch) {
  const s = createStore();
  const ali = s.get(1);
  const vali = s.get(1);
  const a = s.put(1, { ...ali.body, matn: "Ali: non va sut olish" },
    useIfMatch ? ali.etag : undefined);
  const v = s.put(1, { ...vali.body, bajarildi: true },
    useIfMatch ? vali.etag : undefined);
  console.log(`Ali ${a.status}, Vali ${v.status} →`, s.get(1).body);
}

scenario(false);
scenario(true);

Konsolda:

text
Ali 200, Vali 200 → { matn: 'Non olish', bajarildi: true, versiya: 3 }
Ali 200, Vali 412 → { matn: 'Ali: non va sut olish', bajarildi: false, versiya: 2 }

Birinchi qatorda ikkalasi ham 200 oldi, lekin Alining matni yo'q — jim yo'qoldi. Ikkinchi qatorda Vali 412 Precondition Failed oldi: "siz 1-versiyaga yozmoqchisiz, joriy esa 2". Alining o'zgarishi saqlandi. Vali endi qayta o'qiydi, yangi matnni ko'radi va belgisini qo'yadi.

Kanon t26 da xuddi shu tajribani haqiqiy API bilan qilgan: If-Match'siz — Ali 200, Vali 200, "Alining o'zgarishi YO'QOLDI"; If-Match bilan — Ali 200, Vali 412, Alining matni saqlandi.

4.2 Qoidalar

  • If-Match faqat kuchli solishtirish bilan ishlaydi (RFC 9110 §13.1.1). If-Match: W/"3" hech qachon mos kelmaydi → 412.
  • If-Match: * — "resurs mavjud bo'lsa yoz" (versiyasidan qat'i nazar).
  • If-Match umuman yo'q — kanonda shartsiz yoziladi. Sabab: 22-qism frontend'i If-Match yubormaydi, uni buzmaslik kerak. Qattiqroq API sarlavhasiz yozishni 428 Precondition Required (RFC 6585) bilan rad etadi — kanonga kirmadi.
  • 412 javobida joriy ETag qaytariladi — mijoz nima o'zgarganini ko'rishi uchun qayta o'qiydi.

Bu yondashuv optimistik qulflash (optimistic concurrency) deyiladi: hech narsa oldindan qulflanmaydi, faqat yozish paytida "versiya hali o'shami?" tekshiriladi. Postgres'da u UPDATE … WHERE id = $1 AND versiya = $2 bo'ladi — 26-qismda aynan shu ombor interfeysini amalga oshiramiz.

4.3 Vary

Bitta URL turli so'rov sarlavhalariga qarab turli javob bersa, kesh buni bilishi kerak. Vary sarlavhasi aytadi: "javob mana shu so'rov sarlavhalariga bog'liq". Masalan Vary: Accept-Language — o'zbekcha va ruscha javoblar alohida saqlansin. Kanonda cors paketi o'zi Vary: Origin qo'yadi (curl chiqishlarida ko'rgansiz): turli origin'larga turli Access-Control-Allow-Origin. Versiyani sarlavhada bersangiz (oldingi dars), Vary: Api-Version shart bo'lardi — URI versiyasining yana bir afzalligi.

5. API qadami: versiya, ETag, If-Match (holat 26)

5.1 Model va ombor

Vazifa ga versiya: number qo'shildi: yaratilganda 1, har yozuvda +1. Ombor interfeysida yozish natijasi endi uch xil (src/vazifalar/ombor.ts):

ts
export type Shart = { versiya: number };
export type YozishNatijasi<T> =
  | { tur: "ok"; qiymat: T }
  | { tur: "topilmadi" }
  | { tur: "konflikt"; joriy: Vazifa };

export interface VazifaOmbori {
  // ...
  yangila(id: number, ozgarish: VazifaOzgarishi, shart?: Shart):
    Promise<YozishNatijasi<Vazifa>>;
  ochir(id: number, shart?: Shart): Promise<YozishNatijasi<null>>;
}

Nega null yoki istisno emas? 26-qismda UPDATE … WHERE id = $1 AND versiya = $2 0 qator qaytarsa — "topilmadi"mi yoki "konflikt"mi, ombor ajratadi. Xizmat qatlami natijani HTTP xatoga aylantiradi: topilmadi → 404, konflikt → 412 (kod: "versiya-mos-emas", javobda joriy ETag). src/vazifalar/xizmat.ts dagi yordamchi (kanondan):

ts
/** Ombor natijasini qiymatga yoki xatoga aylantiradi:
 *  topilmadi → 404, versiya mos emas → 412 */
function natija<T>(id: number, n: YozishNatijasi<T>): T {
  if (n.tur === "topilmadi") {
    throw new HttpXato(404, `Vazifa ${String(id)} topilmadi`,
      TOPILMADI);
  }
  if (n.tur === "konflikt") {
    const joriy = String(n.joriy.versiya);
    throw new HttpXato(412,
      `Vazifa ${String(id)} boshqa so'rov bilan o'zgargan ` +
      `(joriy versiya ${joriy}) — qayta o'qing`, {
        kod: "versiya-mos-emas",
        sarlavha: "Vazifa o'zgarib ketgan",
        httpSarlavhalar: { ETag: `"${joriy}"` },
      });
  }
  return n.qiymat;
}

yangila, almashtir, ochir metodlari ombor natijasini shu funksiyadan o'tkazadi. TOPILMADI — { kod: "vazifa-topilmadi", sarlavha: "Vazifa topilmadi" }.

5.2 src/umumiy/shartli.ts

Yangi fayl — to'liq (kanondan; uzun izohlar qatorlarga bo'lingan):

ts
// shartli.ts — HTTP kesh va shartli so'rovlar: ETag (versiyadan),
// If-Match (yo'qolgan yangilanishdan himoya), Cache-Control
import type { Request, Response } from "express";
import { HttpXato } from "./http-xato.ts";
import type { Shart } from "../vazifalar/ombor.ts";
import type { Vazifa } from "../vazifalar/vazifa.ts";

/** Kuchli ETag: "3" — vazifa versiyasi. Tana hash'i emas:
 *  hisoblash arzon, Postgres'da ham shu ustun */
export function etag(v: Vazifa): string {
  return `"${String(v.versiya)}"`;
}

/** Bitta vazifa javobi: ETag + Last-Modified; GET'da Express
 *  (fresh) If-None-Match mos kelsa o'zi 304 beradi */
export function vazifaSarlavhalari(res: Response, v: Vazifa): void {
  res.set("ETag", etag(v));
  res.set("Last-Modified", new Date(v.yangilandi).toUTCString());
}

/** API javoblari: brauzer saqlashi mumkin, lekin har safar
 *  tekshirsin (private — umumiy keshlar saqlamasin) */
export function keshYoq(res: Response): void {
  res.set("Cache-Control", "private, no-cache");
}

/**
 * If-Match: "3" → { versiya: 3 }; yo'q yoki "*" → undefined
 * (shartsiz). If-Match KUCHLI solishtirish talab qiladi —
 * W/"3" mos kelmaydi (RFC 9110 §13.1.1)
 */
export function ifMatch(req: Request): Shart | undefined {
  const x = req.get("if-match");
  if (x === undefined || x.trim() === "*") return undefined;
  const m = /^\s*"(\d{1,9})"\s*$/.exec(x);
  if (m?.[1] === undefined) {
    throw new HttpXato(412,
      `If-Match qiymati bu resursga mos emas: ${x.slice(0, 40)}`,
      { kod: "versiya-mos-emas",
        sarlavha: "Vazifa o'zgarib ketgan" });
  }
  return { versiya: Number(m[1]) };
}

W/"3" regex'ga mos kelmaydi — darhol 412. Ro'yxat (GET /v1/vazifalar) uchun Express'ning o'z kuchsiz ETag'i qoladi — 304 ham ishlaydi. Marshrutda: GET — vazifaSarlavhalari + keshYoq; PUT, PATCH, DELETE — xizmat.…(id, …, ifMatch(req)). CORS allowedHeaders ga If-Match, If-None-Match qo'shildi.

Bitta vazifaga curl'dan If-None-Match: "1" bilan kelgan GET qanday yuradi:

Qadam Qayerda Natija
1 handler: xizmat.ol(id) vazifa o'qildi, versiya: 1
2 vazifaSarlavhalari, keshYoq ETag: "1", Last-Modified, Cache-Control: private, no-cache
3 res.json(v) → Express req.fresh If-None-Match ETag ga teng, so'rovda no-cache yo'q — yangi
4 javob 304, tana tashlanadi, sarlavhalar qoladi

Node fetch bilan uchinchi qadam boshqacha: so'rovda Cache-Control: no-cache bor, fresh "yangi emas" deydi va javob 200 bo'ladi.

5.3 Natija (kanon curl, sarlavhalar qisqartirilgan)

bash
curl -i $A/v1/vazifalar/1 -H 'If-None-Match: "1"'
curl -i -X PATCH $A/v1/vazifalar/1 \
  -H 'Content-Type: application/json' \
  -H 'If-Match: "1"' -d '{"bajarildi":true}'
curl -i -X PATCH $A/v1/vazifalar/1 \
  -H 'Content-Type: application/json' \
  -H 'If-Match: "1"' -d '{"matn":"eski nusxadan"}'
text
HTTP/1.1 304 Not Modified
ETag: "1"
Last-Modified: Sat, 24 Oct 2026 05:24:13 GMT
Cache-Control: private, no-cache

HTTP/1.1 200 OK
ETag: "2"
{"id":1,"matn":"Non olish #uy","bajarildi":true,…,"versiya":2}

HTTP/1.1 412 Precondition Failed
ETag: "2"
Content-Type: application/problem+json; charset=utf-8
{"type":"https://vazifalar-api.example/xatolar/versiya-mos-emas","title":"Vazifa o'zgarib ketgan","status":412,"detail":"Vazifa 1 boshqa so'rov bilan o'zgargan (joriy versiya 2) — qayta o'qing","instance":"/v1/vazifalar/1","kod":"versiya-mos-emas"}

Uchinchi so'rov ikkinchisi bilan bir xil If-Match: "1" yubordi — lekin versiya endi 2. 412 va joriy ETag: "2".

5.4 Testlar va commit

test/shartli.test.ts (+3): ETag/304/Cache-Control (xomSorov bilan — fetch emas!), ro'yxat 304, If-Match 200/412/W/"…" 412/*/shartsiz, DELETE 412/204, eskirgan yo'l sarlavhalari (oldingi dars). Hamma testlar /v1/... da. Kanon tekshiruvi (tekshiruv-26): tip 0, lint 0, test 28/28.

bash
npm run check
git add -A
git commit -m "API /v1 ga ko'chdi (eski yo'llar \
Deprecation/Sunset bilan); ETag = versiya, If-Match bilan 412"

6. Ko'p uchraydigan xatolar

1. no-cache ni "keshlama" deb tushunish. "Keshlama" — no-store. no-cache — "saqla, lekin tekshir".

2. Shaxsiy javobda private yo'q. CDN bir foydalanuvchining ma'lumotini boshqasiga beradi.

3. Shartli so'rovni fetch bilan test qilish. Express 200 qaytaradi va test "304 ishlamayapti" deydi. Kod to'g'ri — test vositasi no-cache qo'shgan. node:http, curl yoki supertest (Supertest darsi) ishlating.

4. ETag'ni ma'no o'zgarmaganda ham o'zgartirish. Masalan javobga serverVaqti qo'shish — har so'rovda yangi hash, 304 hech qachon chiqmaydi.

5. If-Match ni kuchsiz solishtirish. W/"3" ni "3" bilan teng deb qabul qilish — RFC 9110 ga zid.

6. 412 o'rniga 409 qaytarish. 409 — umumiy "konflikt" (masalan "ro'yxat to'lgan"). Siz qo'ygan sarlavha shartini server bajara olmadi — bu aniq 412.

7. Mashqlar

Yechimlar kurs/mashqlar/25/26-kesh/ da.

1-mashq (oson): to'ldiring

  • "Saqla, lekin har safar tekshir" direktivasi —
  • "Umuman saqlama" —
  • O'zgarmagan resursga shartli GET javobi —
  • If-Match mos kelmasa —
  • Kuchsiz ETag prefiksi —
Yechim
  • no-cache — 304 bo'lishi mumkin.
  • no-store — bank, parol sahifalari.
  • 304 Not Modified — tanasiz.
  • 412 Precondition Failed.
  • W/ — W/"8-h5Ed…".

2-mashq (o'rta): If-None-Match ni tahlil qilish

Sarlavha bir nechta ETag olishi mumkin: If-None-Match: "1", W/"2", "3" yoki *. noneMatch(header, currentEtag) yozing: mos keladigan biri bo'lsa true (→ 304). If-None-Match da kuchsiz solishtirish ishlatiladi: W/"2" va "2" teng. Bu toza JS — «Ishga tushir» bilan ishlaydi.

Yechim
js
function noneMatch(header, currentEtag) {
  if (header === undefined) return false;
  if (header.trim() === "*") return true;
  const strip = (tag) => tag.trim().replace(/^W\//, "");
  const current = strip(currentEtag);
  return header.split(",").some((tag) => strip(tag) === current);
}

console.log(noneMatch('"1", W/"2", "3"', '"2"'));
console.log(noneMatch('"1", "3"', 'W/"2"'));
console.log(noneMatch("*", '"9"'));
console.log(noneMatch(undefined, '"1"'));

Konsolda:

text
true
false
true
false

Ehtiyot: ETag ichida vergul bo'lishi nazariy jihatdan mumkin; oddiy split(",") bizning ETag'lar (raqam, base64) uchun yetarli.

3-mashq (qiyin): ETag'li mijoz keshi

createCachedClient(fetchFn) yozing: get(url) birinchi marta oddiy so'raydi va { etag, data } ni saqlaydi. Keyingi safar If-None-Match yuboradi; 304 kelsa — saqlangan data ni qaytaradi, 200 kelsa — yangilaydi. Statistikani (hits, misses) yuritsin. Soxta fetch bilan sinang: server versiyasi 2-so'rovdan keyin o'zgaradi.

Yechim
js
function createCachedClient(fetchFn) {
  const cache = new Map();
  const stats = { hits: 0, misses: 0 };
  async function get(url) {
    const saved = cache.get(url);
    const headers = saved ? { "if-none-match": saved.etag } : {};
    const res = await fetchFn(url, { headers });
    if (res.status === 304 && saved) {
      stats.hits++;
      return saved.data;
    }
    stats.misses++;
    const data = await res.json();
    cache.set(url, { etag: res.headers.get("etag"), data });
    return data;
  }
  return { get, stats };
}

let version = 1;
let calls = 0;
async function fakeFetch(url, init) {
  calls++;
  if (calls === 3) version = 2; // kimdir tahrirladi
  const etag = `"${version}"`;
  if (init.headers["if-none-match"] === etag) {
    return new Response(null, { status: 304, headers: { etag } });
  }
  const body = JSON.stringify({ id: 1, versiya: version });
  return new Response(body, { status: 200, headers: { etag } });
}

const client = createCachedClient(fakeFetch);
for (let i = 0; i < 4; i++) {
  const data = await client.get("/v1/vazifalar/1");
  console.log(i + 1, data.versiya);
}
console.log(client.stats);

Konsolda:

text
1 1
2 1
3 2
4 2
{ hits: 2, misses: 2 }

1 — birinchi yuklash (miss), 2 — 304 (hit), 3 — versiya o'zgardi, 200 (miss), 4 — yana 304 (hit). 304 javobida tana null — new Response(null, …) buni to'g'ri ifodalaydi.

4-mashq: API qadami

O'z vazifalar-api ingizda «API qadami» bo'limidagi o'zgarishlarni qiling: versiya maydoni, YozishNatijasi, shartli.ts, xizmatda 404/412, CORS allowedHeaders. Ikki terminal oynasida Ali va Vali bo'lib t26 tajribasini curl bilan takrorlang. Ikkalasi GET qiladi. Ali If-Match bilan PATCH yuboradi (200), Vali eski ETag bilan (412). node:http bilan bitta 304 testini yozing. npm run check toza bo'lgach — holat 26 commit'i.

8. Real ishda

  • GitHub API javoblarda ETag beradi. Hujjatiga ko'ra (2026-10 holatiga), Authorization bilan yuborilgan shartli so'rov 304 olsa, u asosiy so'rov limitiga hisoblanmaydi — ETag'li mijoz ko'proq so'rov yubora oladi.
  • Google Docs, Notion kabi hamkorlik ilovalarida optimistik qulflash o'rniga real vaqtli birlashtirish (CRDT, OT) ishlatiladi. Oddiy CRUD API uchun If-Match + 412 — yetarli va arzon.
  • CDN (Cloudflare) va nginx proxy_cache (nginx darsi) Cache-Control: public, s-maxage ni o'qib, serverga umuman so'rov yubormaydi.
  • TanStack Query (19-qism) — mijoz tomonidagi kesh; HTTP kesh bilan birga ishlaydi, uni almashtirmaydi.
  • Intervyu: "no-cache va no-store farqi?", "Lost update'ni qanday oldini olasiz?" — bu darsdagi ikki asosiy javob.

Xulosa

  • Cache-Control: shaxsiy javob — private; no-cache — tekshirib ishlat; no-store — saqlama.
  • ETag — resurs versiyasi; kuchli "3" (versiyadan) yoki kuchsiz W/"…" (Express — tana hash'idan).
  • If-None-Match mos kelsa — 304, tanasiz; tejaladigani tarmoq, server CPU emas.
  • Node fetch shartli so'rovga Cache-Control: no-cache qo'shadi — Express 304 bermaydi; testda node:http yoki curl.
  • If-Match (kuchli solishtirish) mos kelmasa — 412: yo'qolgan yangilanishga qarshi optimistik qulflash.

Keyingi dars: Bulk va uzoq davom etuvchi operatsiyalar — butun ro'yxatni almashtirish, qisman muvaffaqiyat uchun 207 va uzoq eksport uchun 202 + holat resursi.

Manbalar

  • RFC 9111 "HTTP Caching" (2022-06) — rfc-editor.org/rfc/rfc9111
  • RFC 9110 §13 "Conditional Requests", §8.8.3 ETag (2022-06) — rfc-editor.org/rfc/rfc9110
  • RFC 5861 stale-while-revalidate (2010-05) — rfc-editor.org/rfc/rfc5861
  • RFC 6585 — 428 Precondition Required (2012-04) — rfc-editor.org/rfc/rfc6585
  • Fetch Standard, "HTTP-network-or-cache fetch" — fetch.spec.whatwg.org
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
HTTP caching va shartli so'rovlar: Cache-Control, ETag, 304 va If-Match bilan 412 — IlmHamroh