Mundarija (19)
- Bu darsda
- 1. Nega bu kerak?
- 2. Xavfsiz va idempotent
- 2.1 Ta'riflar
- 2.2 Idempotentlik — natija haqida, javob haqida emas
- 3. PUT va PATCH
- 3.1 PUT — to'liq almashtirish
- 3.2 PATCH — qisman o'zgartirish
- 4. HEAD va OPTIONS
- 5. API qadami: PUT va merge-patch (holat 22, 2-qism)
- 6. Ko'p uchraydigan xatolar
- 7. Mashqlar
- 1-mashq (oson): jadvalni to'ldiring
- 2-mashq (o'rta): mergePatch testlari
- 3-mashq (qiyin): kichik JSON Patch
- 4-mashq: API qadami
- 8. Real ishda
- Xulosa
- Manbalar
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-apiga to'liq almashtirish (PUT) vamerge-patch+jsonni 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):
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:
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:
POST idempotent: false
PUT idempotent: true
PATCH_toggle idempotent: false
PATCH_merge idempotent: true
DELETE idempotent: trueIkki 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-qilendpoint'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:
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:
// 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:
{"narx":37000,"teglar":["issiq"],"tarkib":{"guruch":300,"gosht":200,"sabzi":200}}Natijada to'rt qoida ko'rinadi:
narx— almashtirildi.teglar— massiv butunlay almashtirildi ("milliy"yo'qoldi). Merge Patch massiv ichiga kira olmaydi.tarkib— ichma-ich birlashtirildi:guruchqoldi,goshto'zgardi,sabziqo'shildi.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:
[
{ "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):
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:
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.
src/vazifalar/vazifa.ts— PUT tanasi uchun tip va tekshiruv (kanon):
/** 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.
- Xizmat va router:
// 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));
})- Merge Patch media turi —
src/umumiy/json-kerak.tsto'liq (holat 22):
// 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:
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.
Allowro'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:
nullmaydonni 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
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:
ok
ok
ok
ok
okBeshinchi 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
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:
{"nom":"Osh","narx":37000,"teglar":["issiq","yangi"]}
test: /narx | hujjat: 37000Ikkinchi 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):
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 —
PATCHni uch formatda qabul qiladi: JSON Patch, Merge Patch va o'zining "strategic merge patch" i. GitHub REST esa oddiyapplication/jsontana 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,
nullo'chiradi, massiv butunlay almashadi. - JSON Patch — amallar ro'yxati, massiv elementi va
testsharti bilan, atomik. - HEAD — Express GET'dan yasaydi;
corshar 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
- RFC 9110 — HTTP Semantics, 9.2.1 Safe Methods, 9.2.2 Idempotent Methods: https://www.rfc-editor.org/rfc/rfc9110 (2022-06)
- RFC 5789 — PATCH Method for HTTP: https://www.rfc-editor.org/rfc/rfc5789
- RFC 7396 — JSON Merge Patch (algoritm va A-ilova misollari): https://www.rfc-editor.org/rfc/rfc7396
- RFC 6902 — JSON Patch; RFC 6901 — JSON Pointer: https://www.rfc-editor.org/rfc/rfc6902
- Kanon: holat 22 (
toliqVazifaniTekshir,JSON_TURLARI, PUT/PATCH testlari),tekshiruv-22. PUT/PATCH/DELETE/HEAD/OPTIONS javoblari — biz ishga tushirdik (holat 22 nusxasi, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!