Ders 01 / 16
Girdi Doğrulama ve Kodlama
Güvenilmeyen veri sınırının gerçekleştirilmesi: yirmi iki isteklik bir kümede izin listesiyle engelleme listesinin yanlış geçme ve yanlış kalma sayıları, doğrulamanın uçta, serviste ve depoda durmasının yan etki maliyeti, tek kodlayıcının üç çıktı bağlamında bozduğu değer sayısı ve tek satırlık bir ayarın sessizce açtığı alan.
İçindekiler
Gözlemlenebilirlik kursu ödünç sistemini görünür kıldı, dayanıklılık kursu arızaya dayanmasını sağladı. İki kurs boyunca sisteme giden her istek geçerliydi: yük üreteci sözleşmeye uyan gövdeler gönderdi, kaos denemeleri isteği değil altyapıyı bozdu. Sözleşmeye uymayan bir isteğin sistem içinde nereye kadar gidebildiği hiç ölçülmedi.
Bu dersin konusu o sınırdır. Bölünmüş ödünç sisteminin üç dış ucu var; bu uçlara gelen her gövde, aksi kanıtlanana kadar güvenilmeyen veridir. Açıkların kendisi anlatılmaz — istemci tarafı Frontend Kalitesi, tarama tarafı Kalite ve Test kursunda kuruldu — burada savunmanın gerçekleştirimi ölçülür.
Güvenilmeyen Veri Sınırı
Sözleşme önce yazılır: hangi uç, hangi alanı, hangi biçimde kabul ediyor. Her isteğin kabul mü ret mi olacağı ödünç sisteminin kurallarından gelir ve elle etiketlenir.
BG1 (varsayım): etiketler eksiksiz ve doğrudur. Ölçülen sayılar bu etiket kümesine görelidir ve kümede olmayan bir istek biçimi hakkında bir şey söylemez.
// istekler.mjs — odunc sisteminin uc dis ucu, alan sozlesmesi ve elle etiketli istek kumesi const O = "POST /odunc", K = "GET /katalog", N = "POST /uye/not"; export const SEMA = { [O]: { uyeNo: { t: "d", desen: /^U-\d{5}$/, zorunlu: 1 }, isbn: { t: "d", desen: /^\d{13}$/, zorunlu: 1 }, gun: { t: "s", enAz: 1, enCok: 30 } }, [K]: { q: { t: "d", enCok: 64, zorunlu: 1 }, sira: { t: "d", kume: ["baslik", "yazar", "yil"] }, sayfa: { t: "s", enAz: 1, enCok: 500 } }, [N]: { uyeNo: { t: "d", desen: /^U-\d{5}$/, zorunlu: 1 }, not: { t: "d", enCok: 120, zorunlu: 1 } }, }; // [uc, govde, mesru?] — mesru degeri elle verilmis karardir, olcumden gelmez. export const ISTEKLER = [ [O, { uyeNo: "U-10432", isbn: "9789750718533", gun: 21 }, 1], [O, { uyeNo: "U-10432", isbn: "9789750718533" }, 1], [K, { q: "kayip zaman" }, 1], [K, { q: "O'Neill", sira: "yazar" }, 1], [K, { q: "kalem", sayfa: 3 }, 1], [N, { uyeNo: "U-10432", not: "kapak yipranmis" }, 1], [N, { uyeNo: "U-77120", not: "iadede 5 < 7 sayfa eksik" }, 1], [O, { uyeNo: "U-00001", isbn: "9786050000000", gun: 1 }, 1], [K, { q: "tarih -- 20. yuzyil" }, 1], [N, { uyeNo: "U-31005", not: "kitap; elden birakildi" }, 1], [O, { uyeNo: "U-10432", isbn: "978-975-07-1853-3" }, 1], // tireli mesru bicim [O, { uyeNo: "u-10432", isbn: "9789750718533" }, 0], // kucuk harf [O, { uyeNo: "U-10432", isbn: "978975071853" }, 0], // on iki hane [O, { uyeNo: "U-10432", isbn: "9789750718533", gun: 400 }, 0], [O, { uyeNo: "U-10432", isbn: "9789750718533", gun: "21" }, 0], [O, { uyeNo: ["U-10432"], isbn: "9789750718533" }, 0], [O, { uyeNo: "U-10432", isbn: "9789750718533", ceza: 0 }, 0], [K, { q: "k".repeat(200) }, 0], [K, { q: "kalem", sira: "kitap.ic_not" }, 0], [K, { q: "kalem", sayfa: -2 }, 0], [N, { uyeNo: "U-10432" }, 0], [N, { uyeNo: "U-10432", not: "ok", yetki: "yonetici" }, 0], ].map(([uc, govde, m], i) => ({ no: i + 1, uc, govde, bekleniyor: m ? "kabul" : "ret" })); export const sayim = (e) => ISTEKLER.filter((i) => i.bekleniyor === e).length;
Sözleşme dışı isteklerde zararlı yük yok; yanlış biçim, yanlış tip ve fazladan alan var.
İki Karar Biçimi
İzin listesi sözleşmede yazılı olan dışında hiçbir şeyi geçirmez. Engelleme listesi
adı konmuş kötü biçimleri reddeder, geri kalanı geçirir. bilinmeyenAlan ayarı üçüncü bir
satır üretecek.
// dogrulayici.mjs — ayni istek kumesine bakan iki karar bicimi import { SEMA } from "./istekler.mjs"; // Izin listesi: sozlesmede yazili olan disinda hicbir sey gecmez. // bilinmeyenAlan = "reddet" | "gecir" (ikincisi bu dersin sessiz ayari) export function izinListesi(uc, govde, { bilinmeyenAlan = "reddet" } = {}) { const alanlar = SEMA[uc]; if (!alanlar) return { karar: "ret", neden: "tanimsiz uc" }; const ret = (n) => ({ karar: "ret", neden: n }); for (const [ad, v] of Object.entries(govde)) { const k = alanlar[ad]; if (!k) { if (bilinmeyenAlan === "reddet") return ret(`bilinmeyen alan ${ad}`); continue; } if (k.t === "d") { if (typeof v !== "string") return ret(`${ad} dizge degil`); if (k.desen && !k.desen.test(v)) return ret(`${ad} bicime uymuyor`); if (k.enCok && v.length > k.enCok) return ret(`${ad} cok uzun`); if (k.kume && !k.kume.includes(v)) return ret(`${ad} kume disinda`); } else { if (!Number.isInteger(v)) return ret(`${ad} tam sayi degil`); if (v < k.enAz || v > k.enCok) return ret(`${ad} aralik disinda`); } } for (const [ad, k] of Object.entries(alanlar)) if (k.zorunlu && govde[ad] === undefined) return ret(`${ad} eksik`); return { karar: "kabul", neden: "" }; } // Engelleme listesi: adi konmus kotu bicimler reddedilir, geri kalan her sey gecer. const YASAK = ["<", ">", "'", '"', "--", ";", "..", "\\"]; export function engellemeListesi(uc, govde) { for (const [ad, v] of Object.entries(govde)) { const m = typeof v === "string" ? v : String(v ?? ""); for (const y of YASAK) if (m.includes(y)) return { karar: "ret", neden: `${ad} yasak imge ${y}` }; if (m.length > 256) return { karar: "ret", neden: `${ad} cok uzun` }; } return { karar: "kabul", neden: "" }; } // Yanlis gecme: reddedilmesi gereken istek kabul edildi. // Yanlis kalma: kabul edilmesi gereken istek reddedildi. export function olc(istekler, karar) { const r = { yanlisGecme: 0, yanlisKalma: 0, kacan: [], takilan: [] }; for (const i of istekler) { const c = karar(i.uc, i.govde).karar; if (c === i.bekleniyor) continue; if (i.bekleniyor === "ret") { r.yanlisGecme++; r.kacan.push(i.no); } else { r.yanlisKalma++; r.takilan.push(i.no); } } return r; }
// karsilastir.mjs — ayni 22 istek, uc karar bicimi; ucuncusu ilk ikisinin sessiz ayari import { ISTEKLER, SEMA, sayim } from "./istekler.mjs"; import { izinListesi, engellemeListesi, olc } from "./dogrulayici.mjs"; const artik = (i) => Object.keys(i.govde).filter((a) => !SEMA[i.uc][a]); // sozlesmede olmayan alan const alan = Object.values(SEMA).reduce((t, a) => t + Object.keys(a).length, 0); console.log(`${Object.keys(SEMA).length} uc, ${alan} alan sozlesmesi, ${ISTEKLER.length} istek: ` + `${sayim("kabul")} mesru, ${sayim("ret")} sozlesme disi\n`); const BICIM = [ ["izin listesi", (uc, g) => izinListesi(uc, g)], ["izin listesi (gecir)", (uc, g) => izinListesi(uc, g, { bilinmeyenAlan: "gecir" })], ["engelleme listesi", engellemeListesi], ]; console.log(`${"karar bicimi".padEnd(22)}${"yanlis gecme".padStart(13)}${"yanlis kalma".padStart(14)}${"artik alanli kayit".padStart(20)}${"hata".padStart(6)}`); for (const [ad, f] of BICIM) { const r = olc(ISTEKLER, f); let a = 0, hata = 0; for (const i of ISTEKLER) { try { if (f(i.uc, i.govde).karar === "kabul" && artik(i).length) a++; } catch { hata++; } } console.log(ad.padEnd(22) + String(r.yanlisGecme).padStart(13) + String(r.yanlisKalma).padStart(14) + String(a).padStart(20) + String(hata).padStart(6)); console.log(` gecen sozlesme disi: ${r.kacan.join(", ") || "yok"}`); console.log(` takilan mesru : ${r.takilan.map((n) => `${n} (${f(ISTEKLER[n - 1].uc, ISTEKLER[n - 1].govde).neden})`).join(", ") || "yok"}`); }
3 uc, 8 alan sozlesmesi, 22 istek: 11 mesru, 11 sozlesme disi karar bicimi yanlis gecme yanlis kalma artik alanli kayit hata izin listesi 0 1 0 0 gecen sozlesme disi: yok takilan mesru : 11 (isbn bicime uymuyor) izin listesi (gecir) 2 1 2 0 gecen sozlesme disi: 17, 22 takilan mesru : 11 (isbn bicime uymuyor) engelleme listesi 11 4 2 0 gecen sozlesme disi: 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22 takilan mesru : 4 (q yasak imge '), 7 (not yasak imge <), 9 (q yasak imge --), 10 (not yasak imge ;)
Engelleme listesi on bir sözleşme dışı isteğin hepsini geçirdi: liste yalnızca adını bildiği imgeleri tanır, on iki haneli tanımlayıcıda ya da fazladan bir alanda o imgelerden hiçbiri yok. Aynı liste dört meşru isteği reddetti — bir yazar adındaki kesme işareti, bir nottaki küçüktür imgesi, arama metnindeki iki çizgi, bir noktalı virgül. Engelleme listesi iki sütunda birden kaybediyor.
BG2 (varsayım): izin listesinin sözleşmesi, kümedeki meşru isteklerin biçimine bakarak yazılmıştır; sıfır yanlış geçme kısmen bu yakınlığın sonucudur. Kalıntı on birinci istektir: tireli tanımlayıcı meşrudur, sözleşme onu tanımıyor. İzin listesinin bedeli budur — öngörülmemiş her meşru biçim bir yanlış kalmadır.
Ortadaki satır bu dersin ilk sessiz ayarıdır. bilinmeyenAlan ilkesi gecir yapıldığında
fırlatılan hata yine sıfırdır, ama iki istek kabul edilir ve depoya sözleşmede olmayan iki
alanla iner: bir ceza değeri ve bir yetki değeri. Bu alanları okuyan bir kod bugün yoktur.
Sınırın Yeri
Ödünç isteği üç adımdan geçer: uçta kaydı alınır, serviste katalog rezervi yapılır, depoya yazılır. İlk iki adım geri alınması gereken yan etkilerdir.
BG3 (varsayım): yan etki adım sayısıyla ölçülür, saniyeyle değil; her adımın süresi bu derste sabit sayılır.
// sinir.mjs — ayni dogrulayici, bes ayri sinir; olculen sey yan etkinin nerede durdugu import { ISTEKLER, SEMA } from "./istekler.mjs"; import { izinListesi } from "./dogrulayici.mjs"; const ADIMLAR = ["uc", "servis", "depo"]; // yan etki: istek kaydi, katalog rezervi, yazma function kos(istek, yerlesim, kayitli) { let yanEtki = 0; for (const a of ADIMLAR) { if (a === yerlesim && kayitli.includes(istek.uc) && izinListesi(istek.uc, istek.govde).karar === "ret") return { yanEtki, yazildi: false }; yanEtki++; } return { yanEtki, yazildi: true }; } const TUM = Object.keys(SEMA); const EKSIK = TUM.filter((u) => u !== "POST /uye/not"); // bir uc kayit edilmeyi bekliyor const bozuk = ISTEKLER.filter((i) => i.bekleniyor === "ret"); const mesru = ISTEKLER.filter((i) => i.bekleniyor === "kabul"); console.log(`${bozuk.length} sozlesme disi istek, ${mesru.length} mesru istek, ${ADIMLAR.length} adimli boru hatti`); console.log(`\n${"yerlesim".padEnd(12)}${"kapsanan uc".padStart(12)}${"duran".padStart(7)}${"yazilan bozuk".padStart(15)}${"bosa yan etki".padStart(15)}${"mesru yazma".padStart(13)}`); for (const [ad, yer, kayitli] of [["uc", "uc", TUM], ["uc (eksik)", "uc", EKSIK], ["servis", "servis", TUM], ["depo", "depo", TUM], ["yok", "-", []]]) { const b = bozuk.map((i) => kos(i, yer, kayitli)), m = mesru.map((i) => kos(i, yer, kayitli)); console.log(ad.padEnd(12) + String(kayitli.length).padStart(12) + String(b.filter((r) => !r.yazildi).length).padStart(7) + String(b.filter((r) => r.yazildi).length).padStart(15) + String(b.reduce((t, r) => t + (r.yazildi ? 0 : r.yanEtki), 0)).padStart(15) + String(m.filter((r) => r.yazildi).length).padStart(13)); } const gecen = bozuk.filter((i) => kos(i, "uc", EKSIK).yazildi).map((i) => i.no); console.log(`\nkayit edilmeyen uc POST /uye/not -> depoya inen sozlesme disi kayit: ${gecen.length} (istek ${gecen.join(", ")})`); console.log(`bu yerlesimde uygulamanin urettigi hata sayisi: 0`);
11 sozlesme disi istek, 11 mesru istek, 3 adimli boru hatti yerlesim kapsanan uc duran yazilan bozuk bosa yan etki mesru yazma uc 3 11 0 0 10 uc (eksik) 2 9 2 0 10 servis 3 11 0 11 10 depo 3 11 0 22 10 yok 0 0 11 0 11 kayit edilmeyen uc POST /uye/not -> depoya inen sozlesme disi kayit: 2 (istek 21, 22) bu yerlesimde uygulamanin urettigi hata sayisi: 0
Üç tam yerleşim de aynı sayıda isteği durduruyor; ayrıştıkları yer ne kadar iş boşa gittiği. Uçta doğrulama sıfır yan etkiyle durur, serviste on bir istek kaydını boşa yazar, depoda yirmi iki adımı boşa çalıştırır — bunların on biri katalog rezervidir, yani bir kopya ayrılıp geri bırakılır.
İkinci satır ikinci sessiz ayardır. Doğrulayıcı uç başına kaydediliyorsa üç kayıt noktası vardır ve dördüncü uç eklendiğinde dördüncü kayıt elle yazılmalıdır. Yazılmazsa uygulama çalışır, hiçbir sınama kırılmaz ve iki sözleşme dışı kayıt sessizce depoya iner. Tek boru hattı bu riski bir noktaya indirir.
mesru yazma sütunu son bir şey söylüyor: doğrulamalı her yerleşimde on bir meşru istekten
onu depoya iniyor. Yanlış kalmanın bedeli kaydedilmeyen bir ödünç işlemidir.
Çıktı Kodlama
Doğrulamayı geçen değer güvenli değildir; sözleşmeye uygundur. Üye notu serbest metin kabul eder, çünkü okuyucu notunda tırnak, virgül ve küçüktür imgesi meşru geçer. Bu değer üç ayrı yere yazılıyor: bir işaretleme gövdesine, bir öznitelik tırnağının içine ve virgülle ayrılmış bir dışa aktarım satırına. Ölçü tek sorudur: değer kaba konup geri okunduğunda aynı mı çıkıyor?
// kodlama.mjs — dogrulamayi gecmis mesru degerler, uc ayri cikti baglami // Olcu: deger kaba yerlestirilip kabin kendi kuraliyla geri okundugunda aynisi mi cikiyor? const DEGERLER = ["kapak yipranmis", "iadede 5 < 7 sayfa eksik", 'uye "acele" notu dusmus', "raf 3, kat 2", "ilk satir\nikinci satir", 'bagis, "ikinci el" 5 kitap']; const govde = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); const oznitelik = (s) => govde(s).replace(/"/g, """).replace(/\n/g, " "); const ayrilmis = (s) => `"${s.replace(/"/g, '""')}"`; const coz = (s) => s.replace(/"/g, '"').replace(/ /g, "\n") .replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&"); const ayir = (k) => { // tirnaga saygili alan ayirici const alan = []; let s = "", t = false; for (let i = 0; i < k.length; i++) { const c = k[i]; if (t && c === '"' && k[i + 1] === '"') { s += '"'; i++; } else if (c === '"') t = !t; else if (!t && (c === "," || c === "\n")) { alan.push(s); s = ""; } else s += c; } return [...alan, s]; }; const BAGLAM = [ ["govde", (x) => `<not>${x}</not>`, govde, (k) => { const son = k.indexOf("<", 5); // alan, bir sonraki kap imgesine kadar okunur return k.startsWith("<not>") && k.slice(son) === "</not>" ? coz(k.slice(5, son)) : null; }], ["oznitelik", (x) => `<kayit baslik="${x}"/>`, oznitelik, (k) => { const m = /baslik="([^"]*)"/.exec(k); return m ? coz(m[1]) : null; }], ["ayrilmis", (x) => `U-10432,${x},2026-03-04`, ayrilmis, (k) => { const a = ayir(k); return a.length === 3 ? a[1] : null; }], ]; const dene = (kodlayici) => { const liste = []; for (const [ad, kap, dogru, oku] of BAGLAM) DEGERLER.forEach((d, i) => { if (oku(kap(kodlayici(d, dogru))) !== d) liste.push(`${ad}/${i + 1}`); }); return liste; }; console.log(`${DEGERLER.length} deger x ${BAGLAM.length} baglam = ${DEGERLER.length * BAGLAM.length} yerlestirme\n`); console.log(`${"kodlayici".padEnd(24)}${"geri okunamayan".padStart(16)} yer`); for (const [ad, f] of [["kodlama yok", (d) => d], ["tek kodlayici (govde)", govde], ["baglama gore", (d, dogru) => dogru(d)]]) { const l = dene(f); console.log(`${ad.padEnd(24)}${String(l.length).padStart(16)} ${l.join(" ") || "-"}`); }
6 deger x 3 baglam = 18 yerlestirme kodlayici geri okunamayan yer kodlama yok 7 govde/2 oznitelik/3 oznitelik/6 ayrilmis/3 ayrilmis/4 ayrilmis/5 ayrilmis/6 tek kodlayici (govde) 7 oznitelik/3 oznitelik/6 ayrilmis/2 ayrilmis/3 ayrilmis/4 ayrilmis/5 ayrilmis/6 baglama gore 0 -
Tek kodlayıcı hiçbir şey kazandırmadı: on sekiz yerleştirmenin yedisi hâlâ geri okunamıyor ve liste değişti. Gövde bağlamı düzeldi, ayrılmış değer bağlamında ikinci değer bozuldu — nottaki küçüktür imgesi kaçış dizisine çevrildiği için dışa aktarım satırında ham metin olarak duruyor. Üçüncü sessiz sonuç budur: dosya açılır, satırlar okunur ve içindeki not artık yazılan not değildir.
Doğru gerçekleştirim kodlamayı veri girerken değil, yazarken yapar; girişte kodlanan bir değer ikinci bağlama gittiğinde iki kez kodlanır.
Özet
- Aynı yirmi iki istekte izin listesi 0 yanlış geçme ve 1 yanlış kalma, engelleme listesi 11 yanlış geçme ve 4 yanlış kalma verdi. Engelleme listesinin geçirdiklerinde zararlı imge yoktu; yanlış biçim, yanlış tip ve fazladan alan vardı.
- İzin listesinin bedeli tek yanlış kalmadadır: tireli yazılmış meşru bir tanımlayıcı reddedildi, bir ödünç işlemi kaydedilmedi.
- Doğrulama uçta durduğunda 0, serviste 11, depoda 22 adım boşa çalıştı; durdurulan istek sayısı üçünde de aynıydı. Fark yalnız maliyettedir.
- Tek kodlayıcı üç bağlama yetmedi: 18 yerleştirmenin 7’si geri okunamadı ve bir bağlam düzelirken başkası bozuldu. Kodlama her çıktı bağlamında ayrı yapılır.
- Üç yanlış ayar da sessizdir, toplam fırlatılan hata sıfırdır:
bilinmeyenAlanilkesi 2 artık alanı depoya indirdi, kaydı unutulan bir uç 2 sözleşme dışı kaydı yazdırdı, yanlış bağlamdaki kodlayıcı bir satırı bozdu. Doğrulayıcının kaydı uç başına yapılırsa 3 noktada tekrarlanır, tek boru hattında 1 noktada durur.
Sonraki Adım
Doğrulama sınırı gövdenin biçimini garanti eder. Garanti etmediği şey o değerin sistemin içinde nasıl kullanıldığıdır. Sözleşmeye tamamen uyan bir arama metni, bir sıralama alanı ya da bir bildirim şablonu; bir sorgu metnine, bir komut satırına veya bir şablon gövdesine dizge birleştirmesiyle konduğunda artık veri değil, o dilin bir parçası olur. Sonraki ders bu üç yeri aynı kökten gösterir ve tek kararı ölçer: değer metne birleştirildiğinde ile ayrı bir işlenen olarak geçirildiğinde aynı istek kümesi kaç farklı sonuç üretiyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.