İçeriğe geç
academia.sh

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.
  • step kuralı taban değerden başlayan bir ızgara tanımlar; taban min yazılmışsa odur.
  • badInput alanın hiç değer üretemediği durumdur ve sunucuya ulaşmaz; maxlength yazmayı da engellediği için tooLong seyrek doğar.
  • Tarayıcı geçersiz alanda gönderimi durdurur; novalidate bu 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat