Ders 23 / 25
Yerleşik Doğrulama
Kısıt özniteliklerinin ürettiği geçerlilik durumları, gönderimin durdurulması ve aynı kuralların sunucuda neden yeniden uygulandığı.
İçindekiler
Alanlar adlandırıldı; sıra girilen değerin kabul edilip edilmeyeceğine geldi.
Tarayıcı, gönderim başlamadan önce form içindeki alanları sınar. Bir alan tanımlı bir kısıtı çiğniyorsa gönderim durdurulur ve kullanıcıya bir ileti gösterilir. Bu düzeneğin adı kısıt doğrulaması (constraint validation) ve tümüyle işaretlemede bildirilir; betik gerektirmez.
Bu ders iki yanı birlikte ele alır: tarayıcının uyguladığı kuralları ve aynı kuralların sunucuda neden yeniden uygulanması gerektiğini.
Kısıt Öznitelikleri
Kısıtlar alanlara öznitelik olarak yazılır ve her biri belirli türlerde anlamlıdır.
| Öznitelik | Kısıt |
|---|---|
required |
Değer boş olamaz |
min, max |
Sayısal ve tarihsel alanlarda alt ve üst sınır |
step |
Değerin hangi adımlarla artabileceği |
minlength, maxlength |
Metin uzunluğunun alt ve üst sınırı |
pattern |
Metnin uyması gereken düzenli ifade |
type |
Türün kendi biçim kuralı (email, url) |
pattern değeri bir düzenli ifadedir ve Kabuk Programlama kursundaki Düzenli İfadeler
dersinde tanımlanan kavramları kullanır. Bir ayrım vardır: örüntü değerin tamamına
uygulanır; başına ve sonuna çapa yazmak gerekmez, yazılması da bir şey değiştirmez.
Geçerlilik Durumları
Bir kısıt çiğnendiğinde alan bir geçerlilik durumu (validity state) kazanır. Durumların adları tanımlıdır ve bir alanda birden çoğu aynı anda doğru olabilir.
// dogrulama.mjs — kisit dogrulama kurallarinin modeli const ALANLAR = { istasyon: { tur: "text", required: true, maxlength: 20, pattern: "[a-z-]+" }, eposta: { tur: "email", required: true }, sicaklik: { tur: "number", required: true, min: -60, max: 60, step: 0.1 }, tarih: { tur: "date", required: true }, }; const EPOSTA = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/; const TARIH = /^\d{4}-\d{2}-\d{2}$/; function dogrula(ad, deger) { const kural = ALANLAR[ad]; const durum = []; if (kural.required && deger === "") durum.push("valueMissing"); if (deger === "") return durum; if (kural.tur === "email" && !EPOSTA.test(deger)) durum.push("typeMismatch"); if (kural.tur === "date" && !TARIH.test(deger)) durum.push("typeMismatch"); if (kural.maxlength !== undefined && [...deger].length > kural.maxlength) durum.push("tooLong"); if (kural.pattern && !new RegExp("^(?:" + kural.pattern + ")$", "u").test(deger)) { durum.push("patternMismatch"); } if (kural.tur === "number") { const sayi = Number(deger); if (deger.trim() === "" || Number.isNaN(sayi)) return [...durum, "badInput"]; if (kural.min !== undefined && sayi < kural.min) durum.push("rangeUnderflow"); if (kural.max !== undefined && sayi > kural.max) durum.push("rangeOverflow"); if (kural.step) { const taban = kural.min ?? 0; const adim = Math.round(((sayi - taban) / kural.step) * 1e6) / 1e6; if (Math.abs(adim - Math.round(adim)) > 1e-9) durum.push("stepMismatch"); } } return durum; } const denemeler = [ ["istasyon", "kuzey-yamac"], ["istasyon", ""], ["istasyon", "Kuzey Yamac"], ["istasyon", "cok-uzun-bir-istasyon-adi"], ["eposta", "[email protected]"], ["eposta", "gozlem(at)ornek"], ["sicaklik", "-4.2"], ["sicaklik", "-4.25"], ["sicaklik", "95"], ["sicaklik", "dort"], ["tarih", "2024-03-12"], ["tarih", "12.03.2024"], ]; for (const [ad, deger] of denemeler) { const durum = dogrula(ad, deger); console.log( ad.padEnd(10) + JSON.stringify(deger).padEnd(28) + (durum.length === 0 ? "gecerli" : durum.join(", ")), ); }
istasyon "kuzey-yamac" gecerli istasyon "" valueMissing istasyon "Kuzey Yamac" patternMismatch istasyon "cok-uzun-bir-istasyon-adi" tooLong eposta "[email protected]" gecerli eposta "gozlem(at)ornek" typeMismatch sicaklik "-4.2" gecerli sicaklik "-4.25" stepMismatch sicaklik "95" rangeOverflow sicaklik "dort" badInput tarih "2024-03-12" gecerli tarih "12.03.2024" typeMismatch
Dört satır açıklama gerektirir.
stepMismatch, adım kuralının nasıl işlediğini gösterir. step="0.1" bildirimi
“ondalık kabul edilir” demez; değerin bir taban değerden başlayarak adım katlarına
düşmesini ister. Taban, min yazılmışsa odur — burada -60 — yoksa sıfırdır. -4.25
değeri bu ızgaraya düşmez.
badInput, diğerlerinden ayrı bir durumdur: alan bir değer bile üretememiştir. Bir
sayı alanına sayı olmayan bir metin girildiğinde alanın değeri boş dizgi olur; kullanıcı
bir şey yazmış olsa da alan boş görünür. Bu durum sunucuya hiçbir zaman ulaşmaz.
tooLong durumu tarayıcıda seyrek görülür, çünkü maxlength yazmayı da engeller —
kullanıcı sınırdan fazlasını giremez. Durum ancak kullanıcı değeri yapıştırdığında ya da
davranış katmanı değeri sonradan yazdığında doğar; value özniteliğiyle verilen ve hiç
düzenlenmemiş bir başlangıç değeri bu durumu üretmez. Bu, kısıtın yalnızca gönderim
öncesinde değil, giriş sırasında da işlemesinin bir örneğidir.
Son satır tarih alanının biçim kuralını gösterir: yerel yazım kabul edilmez, alanın ürettiği değer tanımlı biçimdedir.
Gönderimin Durdurulması
Geçersiz bir alan varsa gönderim yapılmaz ve tarayıcı ilk geçersiz alana odaklanıp bir ileti gösterir. İletinin metni tarayıcının kendi diliyle üretilir ve belgeden değiştirilemez; biçimi ve süresi de tarayıcıya bağlıdır.
Bu davranışın iki sınırı vardır. İleti tek seferde yalnızca bir alan için görünür; on alanlık bir formda dokuz hata varsa kullanıcı bunları teker teker keşfeder. İleti kısa sürede kaybolabilir ve kullanıcı geri dönemez.
novalidate özniteliği form üzerine yazıldığında bu davranış kapatılır; gönderim
kısıtlara bakılmadan yapılır. Öznitelik iki durumda anlamlıdır: kısıtları belgede
bildirip denetimi sunucuya bırakmak, ya da hata sunumunu tarayıcıya bırakmadan kendi
denetimini yapmak. İkincisi sonraki dersin konusudur.
İstemci Denetimi Bir Güvence Değildir
Tarayıcının uyguladığı her kural istemcide uygulanır ve istemci kullanıcının denetimindedir. Kısıtları kaldırmak için özel bir araç gerekmez: gönderim, belgeyi kullanmadan da yapılabilir. Girdi Türleri dersinde gösterildiği gibi sunucuya ulaşan şey yalnızca ad–değer dizgileridir; hangi alandan geldikleri, o alanda hangi kısıtların yazılı olduğu bilgisi gövdede yoktur.
Aşağıdaki sunucu aynı kısıtları yeniden uygular ve dört farklı gövdeye verdiği yanıtı yazdırır. Gövdelerin ikisi, belgede yazılı kısıtları çiğneyerek gönderilmiştir.
// sunucu-dogrulama.mjs — ayni kisitlarin sunucuda yeniden uygulanmasi import { createServer } from "node:http"; const ALANLAR = { istasyon: { required: true, maxlength: 20, pattern: "[a-z-]+" }, sicaklik: { required: true, sayisal: true, min: -60, max: 60, step: 0.1 }, }; function dogrula(ciftler) { const hatalar = []; for (const [ad, kural] of Object.entries(ALANLAR)) { const deger = ciftler.get(ad) ?? ""; if (kural.required && deger === "") { hatalar.push([ad, "valueMissing"]); continue; } if (kural.maxlength && [...deger].length > kural.maxlength) hatalar.push([ad, "tooLong"]); if (kural.pattern && !new RegExp("^(?:" + kural.pattern + ")$", "u").test(deger)) { hatalar.push([ad, "patternMismatch"]); } if (kural.sayisal) { const sayi = Number(deger); if (Number.isNaN(sayi)) { hatalar.push([ad, "badInput"]); continue; } if (sayi < kural.min) hatalar.push([ad, "rangeUnderflow"]); if (sayi > kural.max) hatalar.push([ad, "rangeOverflow"]); const adim = (sayi - kural.min) / kural.step; if (Math.abs(adim - Math.round(adim)) > 1e-9) hatalar.push([ad, "stepMismatch"]); } } // Bildirilmemis alanlar: giris listesinde olmamasi gereken adlar. for (const ad of new Set(ciftler.keys())) { if (!(ad in ALANLAR)) hatalar.push([ad, "bilinmeyenAlan"]); } return hatalar; } const sunucu = createServer((istek, yanit) => { const parcalar = []; istek.on("data", (parca) => parcalar.push(parca)); istek.on("end", () => { const ciftler = new URLSearchParams(Buffer.concat(parcalar).toString()); const hatalar = dogrula(ciftler); const durum = hatalar.length === 0 ? 201 : 400; yanit.writeHead(durum, { "content-type": "text/plain; charset=utf-8" }); yanit.end(hatalar.map(([ad, kod]) => ad + ": " + kod).join("\n") || "kayit alindi"); }); }); sunucu.listen(0, "127.0.0.1", async () => { const adres = "http://127.0.0.1:" + sunucu.address().port + "/olcum"; const govdeler = [ "istasyon=kuzey-yamac&sicaklik=-4.2", "istasyon=Kuzey%20Yamac&sicaklik=95", "sicaklik=-4.25", "istasyon=kuzey-yamac&sicaklik=-4.2&yonetici=evet", ]; for (const govde of govdeler) { const yanit = await fetch(adres, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: govde, }); console.log("govde :", govde); console.log("durum :", yanit.status); console.log("yanit :", (await yanit.text()).replace(/\n/g, " | ")); console.log("---"); } sunucu.close(); });
govde : istasyon=kuzey-yamac&sicaklik=-4.2 durum : 201 yanit : kayit alindi --- govde : istasyon=Kuzey%20Yamac&sicaklik=95 durum : 400 yanit : istasyon: patternMismatch | sicaklik: rangeOverflow --- govde : sicaklik=-4.25 durum : 400 yanit : istasyon: valueMissing | sicaklik: stepMismatch --- govde : istasyon=kuzey-yamac&sicaklik=-4.2&yonetici=evet durum : 400 yanit : yonetici: bilinmeyenAlan ---
Üçüncü gövde, belgede required yazılı bir alanı hiç göndermemiştir; ikinci gövde iki
kısıtı birden çiğnemiştir. Tarayıcı bu gönderimleri engellerdi; sunucuya doğrudan
yapıldıklarında engelleyen tek şey sunucunun kendi denetimidir.
Dördüncü gövde ayrı bir kusur türünü gösterir. Formda bulunmayan bir alan gönderilmiştir ve sunucu bunu reddeder. Beklenmeyen alanları sessizce yok saymak yerine reddetmek, gövdeyi doğrudan bir kayda dönüştüren gerçekleştirimlerde ayrıcalık yükseltmeyi önler.
Yanıtın durum kodu da bilgi taşır: HTTP İstek ve Yanıtı dersindeki sınıflandırmaya göre
400 isteğin kusurlu olduğunu, 201 bir kaynağın oluşturulduğunu bildirir.
İki Yerde Tutulan Kural
İstemci ve sunucu denetimleri aynı kuralı iki kez ifade eder ve bu bir yineleme gibi görünür. Görevleri farklıdır.
İstemci denetimi bir kolaylıktır: kullanıcıya gecikmesiz geri bildirim verir ve sunucuya boşa istek gitmesini önler. Kaldırılabilir olması sorun değildir, çünkü kimseyi korumak için orada değildir.
Sunucu denetimi bir kuraldır: verinin tutarlılığını sağlayan tek yerdir. İstemcide ne yazıldığından bağımsız olarak uygulanır.
Yinelemeyi azaltmanın yolu, kuralları tek bir tanımdan üretmektir: alan tanımları bir
veri yapısında tutulur, hem işaretleme hem sunucu denetimi ondan türetilir. Yukarıdaki iki
betikte ALANLAR nesnesi bu tanımın karşılığıdır.
İstasyon Belgesinde
<fieldset> <legend>Ölçüm bilgisi</legend> <p><label for="istasyon">İstasyon kodu</label> <input id="istasyon" name="istasyon" type="text" value="kuzey-yamac" required maxlength="20" pattern="[a-z-]+" aria-describedby="istasyon-yardim"> <span id="istasyon-yardim">Yalnızca küçük harf ve tire.</span></p> <p><label for="deger">Ölçülen değer (°C)</label> <input id="deger" name="deger" type="number" required min="-60" max="60" step="0.1"></p> </fieldset>
Kısıtlar hem kullanıcıya yardım eder hem de belgenin taşıdığı sözleşmeyi bildirir: bu alanın hangi değerleri kabul ettiği belgede yazılıdır.
Özet
- Kısıtlar öznitelik olarak bildirilir ve çiğnendiklerinde adları tanımlı geçerlilik durumları üretir; bir alanda birden çok durum aynı anda doğru olabilir.
stepkuralı taban değerden başlayan bir ızgara tanımlar; tabanminyazılmışsa odur.badInputalanın hiç değer üretemediği durumdur ve sunucuya ulaşmaz;maxlengthyazmayı da engellediği içintooLongseyrek doğar.- Tarayıcı geçersiz alanda gönderimi durdurur;
novalidatebu davranışı kapatır. - İstemci doğrulaması bir kolaylıktır ve kaldırılabilir; verinin tutarlılığını sağlayan tek yer sunucudur ve aynı kısıtlar orada yeniden uygulanır.
Sonraki Adım
Doğrulama, metin ve sayı alanları üzerinde kuruldu. Dosya alanı bu düzenlere uymaz: değeri bir dizgi değildir, gövdeye sığdırılması ayrı bir kodlama gerektirir ve boyut sınırı belgede bildirilemez. Sonraki ders dosya yüklemeyi ele alır ve iki gönderim kodlamasının gövdedeki farkını gerçek baytlar üzerinden gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.