IlmHamroh
JavaScript Full-stack/25-qism. HTTP, Express va REST API19/51-dars17 daqiqa
Mundarija (19)

HTTP metodlari semantikasi: xavfsiz, idempotent, PUT va PATCH farqi

Qisqacha: Har HTTP metodi mijozga va'da beradi. Xavfsiz (safe) metod — GET, HEAD, OPTIONS — serverdagi holatni o'zgartirmaydi. Idempotent metod — GET, HEAD, OPTIONS, PUT, DELETE — ikki marta yuborilsa ham natija bir martadagidek. POST va PATCH bunday va'da bermaydi. PUT resursni to'liq almashtiradi, PATCH esa qisman o'zgartiradi: JSON Merge Patch (RFC 7396) — {"bajarildi": true}, JSON Patch (RFC 6902) — amallar ro'yxati.

Bu darsda

  • Xavfsiz va idempotent metod nima ekanini va nega muhimligini tushuntira olasiz.
  • PUT va PATCH farqini misol bilan ko'rsatasiz.
  • JSON Merge Patch algoritmini yozasiz va JSON Patch'ni taniysiz.
  • HEAD va OPTIONS ni Express qanday javob berishini bilasiz.
  • vazifalar-api ga to'liq almashtirish (PUT) va merge-patch+json ni qo'shasiz.

Oldin bilishingiz kerak: REST prinsiplari va resurslar, HTTP metodlari va status kodlari, Mashhur middleware'lar.

1. Nega bu kerak?

Telefonda "To'lash" tugmasini bosdingiz. Internet sekin, javob kelmayapti. Ilova kutib turdi va so'rovni qayta yubordi. Pul ikki marta yechildimi?

Javob metodga bog'liq. Agar so'rov "17-buyurtma holatini tolandi qil" (PUT) bo'lsa — ikki marta yuborish bir martadagidek. Agar "yangi to'lov yarat" (POST) bo'lsa — ikki to'lov bo'lishi mumkin. Brauzer, proxy va HTTP kutubxonalari shu farqni bilib harakat qiladi. Ular qaysi so'rovni avtomatik qayta yuborish mumkinligini metoddan aniqlaydi.

O'xshatish: lift tugmasi va choy damlash. Lift chaqirish tugmasini besh marta bossangiz ham bitta lift keladi — idempotent. "Bir piyola choy qo'sh" desangiz va buni besh marta takrorlasangiz — besh piyola bo'ladi.

2. Xavfsiz va idempotent

2.1 Ta'riflar

Xavfsiz metod (safe method) — mijoz serverdagi holatni o'zgartirishni so'ramaydi. Faqat o'qish. GET, HEAD, OPTIONS va TRACE.

Idempotent metod — bir xil so'rovni N marta yuborish natijasi serverda 1 marta yuborish bilan bir xil. Barcha xavfsiz metodlar, shuningdek PUT va DELETE.

Ikkalasi ham HTTP standartida (RFC 9110, 9.2.1 va 9.2.2) belgilangan va'da. Server uni buzsa, mijoz baribir unga tayanib harakat qiladi.

Metod Xavfsiz Idempotent vazifalar-api da
GET ha ha ro'yxat, bitta vazifa
HEAD ha ha GET'ning sarlavhalari
OPTIONS ha ha CORS preflight
POST yo'q yo'q yangi vazifa
PUT yo'q ha to'liq almashtirish
PATCH yo'q yo'q* qisman o'zgartirish
DELETE yo'q ha o'chirish

* PATCH standart bo'yicha idempotent emas, lekin ba'zi patch'lar amalda idempotent bo'ladi — pastda ko'ramiz.

2.2 Idempotentlik — natija haqida, javob haqida emas

DELETE /vazifalar/1 ni ikki marta yubordik (holat 22, biz ishga tushirdik):

text
DELETE 1 → 204
DELETE 1 yana → 404  {"xato":"Vazifa 1 topilmadi"}

Javoblar har xil: 204 va 404. Lekin DELETE baribir idempotent. Idempotentlik serverdagi holat haqida: birinchi so'rovdan keyin ham, ikkinchisidan keyin ham 1-vazifa yo'q. Javob kodi farq qilishi mumkin.

Quyidagi model buni ko'rsatadi. Xotiradagi "ombor"ga har metodni ikki marta qo'llaymiz va holatni solishtiramiz. «Ishga tushir» bilan sinang:

js
function yangiOmbor() {
  const non = { matn: "Non", bajarildi: false };
  return { keyingiId: 2, vazifalar: { 1: non } };
}
const amallar = {
  POST: (o) => {
    o.vazifalar[o.keyingiId++] = { matn: "Sut", bajarildi: false };
  },
  PUT: (o) => {
    o.vazifalar[1] = { matn: "Non olish", bajarildi: true };
  },
  PATCH_toggle: (o) => {
    o.vazifalar[1].bajarildi = !o.vazifalar[1].bajarildi;
  },
  PATCH_merge: (o) => { o.vazifalar[1].bajarildi = true; },
  DELETE: (o) => { delete o.vazifalar[1]; },
};
for (const [nom, amal] of Object.entries(amallar)) {
  const bir = yangiOmbor(); amal(bir);
  const ikki = yangiOmbor(); amal(ikki); amal(ikki);
  const teng = JSON.stringify(bir) === JSON.stringify(ikki);
  console.log(`${nom.padEnd(12)} idempotent: ${teng}`);
}

Konsolda:

text
POST         idempotent: false
PUT          idempotent: true
PATCH_toggle idempotent: false
PATCH_merge  idempotent: true
DELETE       idempotent: true

Ikki PATCH'ga qarang. {"bajarildi": true} — "shu qiymatni qo'y" — ikki marta qo'llansa ham natija bir xil. "Holatni teskarisiga almashtir" esa har safar boshqa natija beradi. Shuning uchun standart PATCH'ni umuman idempotent demaydi: hammasi patch'ning mazmuniga bog'liq.

POST'ni idempotent qilish uchun maxsus usul bor — Idempotency-Key sarlavhasi (Idempotency-Key darsi).

Tekshirib ko'ring: Kimdir GET /vazifalar/7/bajarildi-qil endpoint'ini yozdi — u vazifani bajarilgan deb belgilaydi. Qanday xavf bor?

Javob

GET xavfsiz bo'lishi kerak, bu esa holatni o'zgartiradi. Brauzerlar va ba'zi ilovalar sahifadagi havolalarni oldindan yuklaydi (prefetch), qidiruv robotlari havolalar bo'ylab yuradi, kesh GET javobini saqlaydi. Natijada vazifalar foydalanuvchi bosmasdan "bajarilgan" bo'lib qoladi. To'g'risi: PATCH /vazifalar/7 tanasi {"bajarildi": true}.

3. PUT va PATCH

3.1 PUT — to'liq almashtirish

PUT'ning ma'nosi: "shu URI'dagi resurs aynan shu bo'lsin". Tanada resursning to'liq yangi tasviri keladi. Tanada yo'q maydon — "o'zgarmasin" emas, "bo'lmasin" yoki "sukut qiymatiga qaytsin".

Kanon qarori: vazifalar-api da PUT matn va bajarildi ni majburiy qiladi. Biz holat 22 ni ishga tushirib tekshirdik:

text
PUT matn → 422  {"xato":"PUT: bajarildi majburiy (qisman o'zgartirish — PATCH)"}
PUT to'liq → 200  {"id":1,"matn":"Non olish #uy","bajarildi":true,"teglar":["uy"],…}

Birinchi so'rov faqat {"matn":"Non"} yubordi. Server uni "bajarildi ni false qil" deb taxmin qilmadi — 422 bilan rad etdi va xabarda to'g'ri yo'lni aytdi. Nega? Agar yo'q maydon jim false bo'lsa, frontend bilmasdan bajarilgan vazifani "bajarilmagan"ga qaytarib qo'yadi.

id, teglar, yaratildi, yangilandi — server boshqaradigan maydonlar. Ular PUT tanasida kelmaydi. Kelsa — noma'lum maydon (oq ro'yxat, Mashhur middleware'lar darsidagi "ichma-ich forma" xavfi bilan bir g'oya).

3.2 PATCH — qisman o'zgartirish

PATCH (RFC 5789) — "resursga shu o'zgarishlarni qo'lla". Lekin o'zgarishlar qanday formatda yoziladi? RFC 5789 buni belgilamaydi. Ikkita mashhur standart format bor.

JSON Merge Patch (RFC 7396, media turi application/merge-patch+json). Tana — resursga o'xshash obyekt, faqat o'zgaradigan maydonlar bilan:

  • Maydon bor — yangi qiymat.
  • Maydon yo'q — o'zgarmaydi.
  • Maydon null — o'chiriladi.
  • Ichma-ich obyekt — rekursiv birlashtiriladi.

RFC 7396 algoritmi qisqa — standartning o'zida psevdokod bor. Uni JS'ga o'giramiz. «Ishga tushir» bilan sinang:

js
// RFC 7396, 2-bo'lim: MergePatch(Target, Patch)
function mergePatch(target, patch) {
  const obyektmi = patch !== null && typeof patch === "object";
  if (!obyektmi || Array.isArray(patch)) {
    return patch; // obyekt emas — butunlay almashtiradi
  }
  const natija = (target !== null && typeof target === "object" &&
    !Array.isArray(target)) ? { ...target } : {};
  for (const [kalit, qiymat] of Object.entries(patch)) {
    if (qiymat === null) delete natija[kalit];
    else natija[kalit] = mergePatch(natija[kalit], qiymat);
  }
  return natija;
}

const taom = { nom: "Osh", narx: 35000, teglar: ["issiq", "milliy"],
  tarkib: { guruch: 300, gosht: 150 } };
const patch = { narx: 37000, teglar: ["issiq"],
  tarkib: { gosht: 200, sabzi: 200 }, nom: null };
console.log(JSON.stringify(mergePatch(taom, patch)));

Konsolda:

text
{"narx":37000,"teglar":["issiq"],"tarkib":{"guruch":300,"gosht":200,"sabzi":200}}

Natijada to'rt qoida ko'rinadi:

  1. narx — almashtirildi.
  2. teglar — massiv butunlay almashtirildi ("milliy" yo'qoldi). Merge Patch massiv ichiga kira olmaydi.
  3. tarkib — ichma-ich birlashtirildi: guruch qoldi, gosht o'zgardi, sabzi qo'shildi.
  4. nom: null — maydon o'chirildi.

Merge Patch'ning ikki cheklovi shu yerdan chiqadi: massivdagi bitta elementni o'zgartirib bo'lmaydi va maydonga null qiymatini berib bo'lmaydi (null "o'chir" degani).

JSON Patch (RFC 6902, application/json-patch+json). Tana — amallar ro'yxati:

json
[
  { "op": "test", "path": "/narx", "value": 35000 },
  { "op": "replace", "path": "/narx", "value": 37000 },
  { "op": "remove", "path": "/teglar/1" },
  { "op": "add", "path": "/teglar/-", "value": "yangi" }
]

Amallar: add, remove, replace, move, copy, test. path — JSON Pointer (RFC 6901): /teglar/1 — teglar massivining 1-elementi, /teglar/- — massiv oxiri. test — shart: narx hozir 35 000 bo'lmasa, butun patch rad etiladi. Ro'yxat atomik: bitta amal xato bersa, hech biri qo'llanmaydi.

Merge Patch (7396) JSON Patch (6902)
Ko'rinishi Resursga o'xshash obyekt Amallar massivi
Massiv elementi Yo'q — butun massiv Ha — /teglar/1
null qiymat Yo'q — "o'chir" ma'nosida Ha
Shart (test) Yo'q Ha
O'qish va yozish Oson Murakkabroq

Ko'p API'lar uchun Merge Patch yetadi. JSON Patch — murakkab hujjatlar va massiv ichidagi o'zgarishlar uchun (masalan, Kubernetes ikkalasini ham qo'llaydi).

Tekshirib ko'ring: Vazifada teglar: ["uy", "bozor"] bor. Merge Patch bilan faqat "bozor" ni olib tashlash uchun qanday tana yuborasiz?

Javob

{"teglar": ["uy"]} — massiv to'liq almashtiriladi, shuning uchun qolishi kerak bo'lgan elementlarni ham yuborasiz. Bu "poyga" (race) xavfini tug'diradi: ikki mijoz bir vaqtda massivni o'zgartirsa, biri ikkinchisining o'zgarishini yo'qotadi. JSON Patch'da esa {"op": "remove", "path": "/teglar/1"} va oldidan test bilan tekshiruv. vazifalar-api da teglar matndan hisoblanadi — bu muammo yo'q.

4. HEAD va OPTIONS

HEAD — GET bilan bir xil, lekin javob tanasiz. Faqat sarlavhalar: hajm, tur, ETag. Katta faylni yuklamasdan "o'zgarganmi, qancha hajmda?" deb so'rash uchun. Express HEAD'ni GET marshrutidan o'zi yasaydi. Biz node:http bilan tekshirdik (holat 22):

text
HEAD /vazifalar/2 → 200 content-length: 153 content-type: application/json; charset=utf-8 tana uzunligi: 0
GET /vazifalar/2 → 200 content-length: 153 {"id":2,"matn":"Sut #uy #bozor",…}

Content-Length: 153 ikkalasida bir xil, lekin HEAD'da tana 0 bayt. Handler kodi ikkalasida ham ishlaydi — Express tanani yubormaydi. Demak HEAD so'rovi og'ir hisob qiladigan handler'ni ham ishga tushiradi.

OPTIONS — "bu URI'da qaysi metodlar bor?". Javobda Allow sarlavhasi. Amalda OPTIONS ko'proq CORS preflight uchun ishlatiladi (Mashhur middleware'lar).

Biz bitta kutilmagan narsani topdik. Origin sarlavhasisiz oddiy OPTIONS yubordik:

text
OPTIONS (Origin'siz) → 204 allow: undefined acam: GET,HEAD,PUT,PATCH,POST,DELETE body: ""

cors middleware'i har qanday OPTIONS so'roviga o'zi 204 qaytardi. Express'ning o'z avtomatik javobi (marshrutdagi metodlar bilan Allow sarlavhasi) ishlamadi — so'rov routerga yetmadi. Access-Control-Allow-Methods esa marshrutning haqiqiy metodlarini emas, cors ning umumiy ro'yxatini ko'rsatdi. Bu brauzer uchun muammo emas. Lekin Allow ni OPTIONS orqali o'qimoqchi bo'lgan mijoz uchun noto'g'ri ma'lumot. vazifalar-api da haqiqiy Allow 405 javobida bor — keyingi darsda.

flowchart TD
  M["Metod"] --> S{"Holatni<br/>o'zgartiradimi?"}
  S -- "yo'q" --> X["Xavfsiz: GET, HEAD, OPTIONS<br/>kesh, qayta urinish"]
  S -- "ha" --> I{"2 marta = 1 marta?"}
  I -- "ha" --> Y["Idempotent: PUT, DELETE<br/>qayta urinish mumkin"]
  I -- "yo'q" --> N["POST, PATCH<br/>Idempotency-Key bilan"]

Diagrammada har metod qayerga tushishini va mijoz unga qanday munosabatda bo'lishini kuzating. Pastki o'ng tugun — eng xavflisi: tarmoq uzilganda bu so'rovlarni ko'r-ko'rona qayta yuborib bo'lmaydi.

5. API qadami: PUT va merge-patch (holat 22, 2-qism)

Hali commit qilmang — holat 22 ning commiti Pagination darsida.

  1. src/vazifalar/vazifa.ts — PUT tanasi uchun tip va tekshiruv (kanon):
ts
/** PUT — to'liq almashtirish: hamma yoziladigan maydon majburiy */
export type ToliqVazifa = { matn: string; bajarildi: boolean };

/** PUT tanasi: matn VA bajarildi majburiy (yo'q maydon "o'zgarmasin"
 *  emas — PUT butun resursni almashtiradi) */
export function toliqVazifaniTekshir(
  tana: unknown,
): Tekshiruv<ToliqVazifa> {
  const t = yangiVazifaniTekshir(tana);
  if (!t.ok) return t;
  if (t.qiymat.bajarildi === undefined) {
    return { ok: false,
      xato: "PUT: bajarildi majburiy (qisman o'zgartirish — PATCH)" };
  }
  return { ok: true,
    qiymat: { matn: t.qiymat.matn, bajarildi: t.qiymat.bajarildi } };
}

POST tekshiruvi qayta ishlatildi: oq ro'yxat, matn uzunligi, bajarildi turi bir joyda.

  1. Xizmat va router:
ts
// xizmat.ts
/** PUT: butun resurs almashtiriladi — idempotent */
almashtir: (id: number, toliq: ToliqVazifa): Promise<Vazifa> =>
  yangila(id, toliq),

// marshrut.ts
// PUT — to'liq almashtirish (idempotent)
.put(jsonKerak, async (req, res) => {
  const id = idniOl(req.params.id);
  const t = toliqVazifaniTekshir(req.body);
  if (!t.ok) throw new HttpXato(422, t.xato);
  res.json(await xizmat.almashtir(id, t.qiymat));
})
  1. Merge Patch media turi — src/umumiy/json-kerak.ts to'liq (holat 22):
ts
// json-kerak.ts — route darajasidagi middleware: tanali so'rov
// faqat JSON bo'lsin (415)
import type { RequestHandler } from "express";
import { HttpXato } from "./http-xato.ts";

// Express 5: Content-Type JSON bo'lmasa express.json() tanani
// o'qimaydi va req.body = undefined — xato YO'Q. Shuning uchun
// turini o'zimiz tekshiramiz (req.is: JSON — "application/json",
// boshqa — false, tana yo'q — null)
// JSON Merge Patch (RFC 7396) o'z media turiga ega — PATCH uchun
// ikkalasi ham qabul qilinadi
export const JSON_TURLARI =
  ["application/json", "application/merge-patch+json"];

export const jsonKerak: RequestHandler = (req, _res, next) => {
  const tur = req.is(JSON_TURLARI);
  if (tur === false || tur === null) {
    throw new HttpXato(415,
      "Content-Type: application/json bo'lishi kerak");
  }
  next();
};

Holat 17 da tekshiruv bitta qator edi: req.is("application/json") !== "application/json". req.is() ga massiv bersangiz, mos kelgan turni qaytaradi.

Va ilova.ts da express.json({ limit: "16kb", type: JSON_TURLARI }) — aks holda express.json() merge-patch tanasini o'qimaydi va req.body undefined bo'ladi. PATCH tekshiruvi (ozgarishniTekshir) holat 13 dan beri Merge Patch qoidasida ishlaydi: yo'q maydon — o'zgarmaydi. null esa bizda 422: matn va bajarildi majburiy maydonlar, ularni o'chirib bo'lmaydi.

PATCH so'rovi qaysi middleware'lardan o'tishini ikki xil Content-Type bilan kuzating:

Qadam Middleware merge-patch+json json-patch+json
1 express.json({ type: JSON_TURLARI }) tur ro'yxatda — tana o'qiladi tur ro'yxatda yo'q — tana o'qilmaydi
2 jsonKerak req.is() turni topdi — next() req.is() → false — 415 tashlanadi
3 .patch(...) handler ozgarishniTekshir — 200 yoki 422 yetib bormaydi
4 xato ishlovchi ishlamaydi 415 javobini yozadi

Ikkinchi ustunda so'rov handler'gacha yetib boradi. Uchinchisida esa ikkinchi qadamda to'xtaydi.

Biz holat 22 da to'rt PATCH yubordik. Har qatorda statusga va xabar matniga qarang:

text
PATCH merge → 200  {"id":1,"matn":"Non olish #uy","bajarildi":false,…}
PATCH null → 422  {"xato":"matn — satr bo'lishi kerak"}
PATCH bo'sh → 422  {"xato":"o'zgarish yo'q: matn yoki bajarildi"}
PATCH json-patch → 415  {"xato":"Content-Type: application/json bo'lishi kerak"}

JSON Patch (application/json-patch+json) — 415: kanon uni qo'llamaydi, Merge Patch yetarli. 415 xabari aniqroq bo'lishi mumkin edi ("json yoki merge-patch+json") — buni 4-mashqda tuzatasiz.

  1. Allow ro'yxati: metodYoq("GET, HEAD, PUT, PATCH, DELETE").

6. Ko'p uchraydigan xatolar

Xato Oqibat To'g'risi
GET holatni o'zgartiradi Prefetch, robot, kesh "bosib" yuboradi PATCH/POST
PUT'da yo'q maydon jim false/"" Ma'lumot bilmasdan o'chadi 422 yoki PATCH
PATCH'ni PUT kabi yozish Qisman tana boshqa maydonlarni o'chiradi Merge Patch qoidasi
express.json() faqat application/json merge-patch tanasi undefined type: [...]
POST'ni ko'r-ko'rona qayta yuborish Ikki buyurtma, ikki to'lov Idempotency-Key
DELETE ikkinchi marta 404 — "idempotent emas" deb o'ylash Noto'g'ri tushuncha Holat bir xil — idempotent

7. Mashqlar

Yechimlar kurs/mashqlar/25/19-http-metodlar/ da.

1-mashq (oson): jadvalni to'ldiring

  • GET — xavfsiz: [:ha], idempotent: [:ha]
  • PUT — xavfsiz: [:yo'q], idempotent: [:ha]
  • POST — idempotent:
  • Merge Patch'da maydonni o'chirish qiymati —
Yechim
  • GET — ha / ha: faqat o'qiydi.
  • PUT — yo'q / ha: o'zgartiradi, lekin ikki marta qo'llash bir xil natija.
  • POST — yo'q: har safar yangi resurs.
  • null — RFC 7396: null maydonni olib tashlaydi.

2-mashq (o'rta): mergePatch testlari

RFC 7396 ning A-ilovasida (Appendix A) misollar jadvali bor. Ulardan beshtasini «PUT va PATCH» bo'limidagi mergePatch bilan tekshiring: {"a":"b"} + {"a":"c"} → {"a":"c"}; {"a":"b"} + {"a":null} → {}; {"a":["b"]} + {"a":"c"} → {"a":"c"}; ["a","b"] + ["c","d"] → ["c","d"]; {"e":null} + {"a":1} → {"e":null,"a":1}. Har biri uchun ok yoki farqni chiqaring.

Yechim
js
function mergePatch(target, patch) {
  const obyektmi = patch !== null && typeof patch === "object";
  if (!obyektmi || Array.isArray(patch)) {
    return patch;
  }
  const natija = (target !== null && typeof target === "object" &&
    !Array.isArray(target)) ? { ...target } : {};
  for (const [kalit, qiymat] of Object.entries(patch)) {
    if (qiymat === null) delete natija[kalit];
    else natija[kalit] = mergePatch(natija[kalit], qiymat);
  }
  return natija;
}

const misollar = [
  ['{"a":"b"}', '{"a":"c"}', '{"a":"c"}'],
  ['{"a":"b"}', '{"a":null}', "{}"],
  ['{"a":["b"]}', '{"a":"c"}', '{"a":"c"}'],
  ['["a","b"]', '["c","d"]', '["c","d"]'],
  ['{"e":null}', '{"a":1}', '{"e":null,"a":1}'],
];
for (const [t, p, kutilgan] of misollar) {
  const natija = mergePatch(JSON.parse(t), JSON.parse(p));
  const haqiqiy = JSON.stringify(natija);
  const farq = `${haqiqiy} ≠ ${kutilgan}`;
  console.log(haqiqiy === kutilgan ? "ok" : farq);
}

Konsolda:

text
ok
ok
ok
ok
ok

Beshinchi misol muhim: target'dagi "e": null qoladi — null faqat patchda "o'chir" degani.

3-mashq (qiyin): kichik JSON Patch

jsonPatch(hujjat, amallar) ni yozing — faqat test, replace, add (massiv oxiriga - bilan ham) va remove, bitta darajali yo'llar uchun (/narx, /teglar/1, /teglar/-). Atomik bo'lsin: nusxada ishlang va xato bo'lsa asl hujjat o'zgarmasin. test mos kelmasa — Error("test: /narx"). Sinov: «PUT va PATCH» bo'limidagi JSON Patch misolidagi to'rt amal; keyin narx allaqachon o'zgargan holda yana bir marta. Yordam: structuredClone — chuqur nusxa.

Yechim
js
function jsonPatch(hujjat, amallar) {
  const nusxa = structuredClone(hujjat); // atomiklik: asl o'zgarmaydi
  for (const { op, path, value } of amallar) {
    const [, kalit, indeks] = path.split("/");
    const ota = indeks === undefined ? nusxa : nusxa[kalit];
    const joy = indeks === undefined ? kalit : indeks;
    if (op === "test") {
      if (JSON.stringify(ota[joy]) !== JSON.stringify(value)) {
        throw new Error(`test: ${path}`);
      }
    } else if (op === "replace") {
      ota[joy] = value;
    } else if (op === "add") {
      if (Array.isArray(ota)) {
        ota.splice(joy === "-" ? ota.length : Number(joy), 0, value);
      } else ota[joy] = value;
    } else if (op === "remove") {
      if (Array.isArray(ota)) ota.splice(Number(joy), 1);
      else delete ota[joy];
    }
  }
  return nusxa;
}

const taom = { nom: "Osh", narx: 35000, teglar: ["issiq", "milliy"] };
const amallar = [
  { op: "test", path: "/narx", value: 35000 },
  { op: "replace", path: "/narx", value: 37000 },
  { op: "remove", path: "/teglar/1" },
  { op: "add", path: "/teglar/-", value: "yangi" },
];
const yangi = jsonPatch(taom, amallar);
console.log(JSON.stringify(yangi));
try {
  jsonPatch(yangi, amallar);
} catch (xato) {
  console.log(xato.message, "| hujjat:", yangi.narx);
}

Konsolda:

text
{"nom":"Osh","narx":37000,"teglar":["issiq","yangi"]}
test: /narx | hujjat: 37000

Ikkinchi chaqiruvda test narx 35 000 emasligini ko'rdi va butun patch rad etildi — hech bir amal qo'llanmadi. Bu optimistik qulflash g'oyasi: "agar men oxirgi ko'rgan holat hali ham shu bo'lsa — o'zgartir". HTTP'da xuddi shu g'oya If-Match sarlavhasi bilan qilinadi (HTTP caching darsi).

4-mashq: API qadami

toliqVazifaniTekshir, xizmat.almashtir va .put() marshrutini qo'shing; JSON_TURLARI ni jsonKerak va express.json({ type }) ga ulang; Allow ro'yxatiga PUT. 415 xabarini aniqlashtiring: "Content-Type: application/json yoki application/merge-patch+json bo'lishi kerak". Test: PUT faqat matn bilan → 422; to'liq tana bilan ikki marta → ikkalasi 200 va bir xil matn; merge-patch {"matn":null} → 422. Commit qilmang.

Yechim

Kanon testi (test/rest.test.ts):

ts
test("PUT — to'liq; PATCH — merge-patch+json ham", async () => {
  const yol = `${asos}/vazifalar/1`;
  assert.equal(
    (await fetch(yol, jsonYubor("PUT", { matn: "Non" }))).status,
    422);
  const tana = { matn: "Non olish #uy", bajarildi: false };
  const birinchi = await fetch(yol, jsonYubor("PUT", tana));
  assert.equal(birinchi.status, 200);
  const ikkinchi = (await (await fetch(yol, jsonYubor("PUT", tana)))
    .json()) as Vazifa;
  assert.equal(ikkinchi.matn, "Non olish #uy"); // idempotent
  const mp = (body: string) => fetch(yol, { method: "PATCH", body,
    headers: { "Content-Type": "application/merge-patch+json" } });
  assert.equal((await mp('{"bajarildi":false}')).status, 200);
  assert.equal((await mp('{"matn":null}')).status, 422);
});

415 xabari — kanonda hali eski matn (application/json bo'lishi kerak); o'zgartirish sizning qadamingiz. Testlarda xabar matni tekshirilmagani uchun ular buzilmaydi.

8. Real ishda

  • Qayta urinish siyosati: axios-retry, ky, undici — sukut bo'yicha faqat idempotent metodlarni qayta yuboradi. Siz yozgan qayta urinish ham shunday qilishi kerak.
  • To'lov API'lari (Click, Payme) — POST bilan, lekin har so'rovda tranzaksiya ID'si: takror kelgan so'rov tanilib, ikkinchi marta bajarilmaydi.
  • Kubernetes — PATCH ni uch formatda qabul qiladi: JSON Patch, Merge Patch va o'zining "strategic merge patch" i. GitHub REST esa oddiy application/json tana bilan ishlaydi — mazmuni Merge Patch qoidasiga yaqin: yuborilmagan maydon o'zgarmaydi.
  • Intervyuda "PUT va PATCH farqi?" — klassik savol.

Xulosa

  • Xavfsiz: GET, HEAD, OPTIONS — holat o'zgarmaydi. Idempotent: xavfsizlar + PUT, DELETE — N marta = 1 marta (javob kodi farq qilishi mumkin).
  • PUT — to'liq almashtirish; yo'q maydonni taxmin qilmang (vazifalar-api: 422).
  • PATCH — qisman; Merge Patch: yo'q maydon o'zgarmaydi, null o'chiradi, massiv butunlay almashadi.
  • JSON Patch — amallar ro'yxati, massiv elementi va test sharti bilan, atomik.
  • HEAD — Express GET'dan yasaydi; cors har OPTIONS'ga o'zi javob beradi.

Keyingi dars: Status kodlarni to'g'ri tanlash — 201 va Location, 204, 400 va 422 farqi, 405 + Allow, 409, 429 va Retry-After.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
HTTP metodlari semantikasi: xavfsiz, idempotent, PUT va PATCH farqi — IlmHamroh