İçeriğe geç
academia.sh

Ders 11 / 34

Durum Kodu Seçimi

Aynı senaryo kümesinin iki farklı kod eşlemesiyle yanıtlanması ve farkın istemcide ölçülmesi: yakın kodların ayrımı, 2xx ailesinin anlamları, yanlış eşlemenin ürettiği sessiz başarı ve boşa giden yeniden denemeler.

İçindekiler

Bir önceki ders yöntemin taşıdığı sözleri ölçtü ve bir eksikle bitti: eşleşmeyen her şeye 404 dönen bir sunucu, kaynağın olmamasıyla yöntemin uygulanamamasını aynı sayıya indirgiyordu. Aynı ders boyunca 201, 204 ve 409 kodları da gerekçe verilmeden kullanıldı.

Bu ders o gerekçeyi kurar. Durum kodu, yanıtın istemci tarafından yorumlanmadan okunabilen tek bölümüdür. Gövdedeki hata alanı insan tarafından okunmak üzere yazılır ve her serviste farklıdır; durum kodu ise standarttır ve istek katmanı, önbellek, yeniden deneme düzeneği gibi bileşenler kararlarını ona bakarak verir. Kodun yanlış seçilmesi, bu bileşenlerin yanlış karar vermesi demektir — ve bu, ölçülebilir bir farktır.

Aynı Senaryolar, İki Eşleme

Aşağıdaki sunucu tek bir dosyadır ve iki kipte çalışır. dogru kipinde her sonuç kendi koduyla bildirilir. gevsek kipinde ise yaygın bir kolaycılık uygulanır: bulunamayan şeyler için 200 ve gövdede bir hata alanı, geri kalan her başarısızlık için 500.

// kod-sunucusu.mjs — ayni senaryolari iki farkli kod esleme kipinde yanitlar
// Kullanim: node kod-sunucusu.mjs <baglanti-noktasi> <dogru|gevsek>
import { createServer } from "node:http";
import { DatabaseSync } from "node:sqlite";

const [NOKTA, KIP] = [Number(process.argv[2]), process.argv[3]];
const UYE_SINIRI = 2;
const db = new DatabaseSync("kutuphane.db");

const govdeOku = (istek) => new Promise((coz) => {
  let veri = ""; istek.on("data", (p) => (veri += p));
  istek.on("end", () => coz(veri));
});
const gonder = (yanit, kod, nesne, ekBaslik = {}) => {
  yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8", ...ekBaslik });
  yanit.end(nesne === null ? "" : JSON.stringify(nesne));
};
// Gevsek kip: her sonucu 200 ya da 500 ile bildirir, ayrimi govdeye birakir.
const bildir = (yanit, kod, nesne, ekBaslik = {}) => {
  if (KIP === "dogru") return gonder(yanit, kod, nesne, ekBaslik);
  if (kod >= 400 && kod < 500) return gonder(yanit, kod === 404 ? 200 : 500, nesne ?? {});
  return gonder(yanit, 200, nesne ?? { sonuc: "tamam" });
};

const sunucu = createServer(async (istek, yanit) => {
  const yol = new URL(istek.url, "http://127.0.0.1").pathname;
  const kitap = /^\/kitaplar\/([^/]+)$/.exec(yol);
  const odunc = /^\/oduncler\/(\d+)$/.exec(yol);

  if (istek.method === "GET" && kitap) {
    const k = db.prepare("SELECT * FROM kitap WHERE isbn = ?").get(kitap[1]);
    return k ? bildir(yanit, 200, k) : bildir(yanit, 404, { hata: "kitap_yok" });
  }

  if (istek.method === "POST" && yol === "/oduncler") {
    const tur = istek.headers["content-type"] ?? "";
    if (!tur.startsWith("application/json"))
      return bildir(yanit, 415, { hata: "desteklenmeyen_tur", beklenen: "application/json" });
    const ham = await govdeOku(istek);
    let g;
    try { g = JSON.parse(ham); }
    catch { return bildir(yanit, 400, { hata: "govde_cozumlenemedi" }); }

    if (!db.prepare("SELECT 1 FROM kitap WHERE isbn = ?").get(g.isbn))
      return bildir(yanit, 422, { hata: "dogrulama", alan: "isbn", neden: "katalogda_yok" });

    const acik = db.prepare("SELECT COUNT(*) AS n FROM odunc WHERE uye = ? AND iade IS NULL").get(g.uye).n;
    if (acik >= UYE_SINIRI)
      return bildir(yanit, 409, { hata: "uye_siniri", sinir: UYE_SINIRI, acik });

    const s = db.prepare("INSERT INTO odunc (uye, isbn, verilis, iade) VALUES (?,?,?,NULL)")
                .run(g.uye, g.isbn, g.verilis);
    const id = Number(s.lastInsertRowid);
    return bildir(yanit, 201, { id }, { location: `/oduncler/${id}` });
  }

  if (istek.method === "DELETE" && odunc) {
    db.prepare("DELETE FROM odunc WHERE id = ?").run(Number(odunc[1]));
    return bildir(yanit, 204, null);
  }

  // Kaynak var ama yontem uygulanamiyor: bulunamadi degil.
  if (/^\/uyeler\/[^/]+\/durum$/.test(yol))
    return bildir(yanit, 405, { hata: "yontem_uygulanamaz" }, { allow: "GET" });

  bildir(yanit, 404, { hata: "yol_yok" });
});

sunucu.listen(NOKTA, "127.0.0.1", () => console.log(`${KIP} kip 127.0.0.1:${NOKTA}`));

Karşı tarafta, Uygulama Mimarisi kursundaki istek katmanının sadeleştirilmiş bir sürümü durur. Tek kuralı vardır: 5xx sınıfı yeniden denenebilir sayılır, 4xx sınıfı sayılmaz.

// istemci.mjs — durum koduna gore karar veren istemci; yeniden denemeleri sayar
// Kullanim: node istemci.mjs <taban-adres>
const TABAN = process.argv[2];
const JSON_BASLIK = { "content-type": "application/json" };

const SENARYOLAR = [
  ["oku",     "GET",    "/kitaplar/978-0131103627", null, null],
  ["yok",     "GET",    "/kitaplar/000-0000000000", null, null],
  ["olustur", "POST",   "/oduncler", JSON_BASLIK, '{"uye":"U-1002","isbn":"978-0131103627","verilis":"2026-03-10"}'],
  ["sinir",   "POST",   "/oduncler", JSON_BASLIK, '{"uye":"U-1002","isbn":"978-0131103627","verilis":"2026-03-11"}'],
  ["isbn",    "POST",   "/oduncler", JSON_BASLIK, '{"uye":"U-1001","isbn":"000-0000000000","verilis":"2026-03-10"}'],
  ["bozuk",   "POST",   "/oduncler", JSON_BASLIK, "{bozuk"],
  ["tur",     "POST",   "/oduncler", { "content-type": "text/plain" }, "merhaba"],
  ["sil",     "DELETE", "/oduncler/2", null, null],
  ["yontem",  "DELETE", "/uyeler/U-1001/durum", null, null],
];

let toplamIstek = 0, sessizBasari = 0;
for (const [ad, yontem, yol, baslik, govde] of SENARYOLAR) {
  let deneme = 0, yanit;
  do {
    deneme++; toplamIstek++;
    yanit = await fetch(TABAN + yol, { method: yontem, headers: baslik ?? {}, body: govde });
  } while (yanit.status >= 500 && deneme < 4);

  const metin = await yanit.text();
  const basarili = yanit.status < 400;
  const govdedeHata = metin.includes('"hata"');
  if (basarili && govdedeHata) sessizBasari++;
  const konum = yanit.headers.get("location");
  console.log(
    `${ad.padEnd(9)} ${String(yanit.status).padEnd(4)} ` +
    `${(basarili ? "basari" : "hata").padEnd(7)} deneme: ${deneme}` +
    (konum ? `  location: ${konum}` : ""),
  );
}
console.log(`toplam istek: ${toplamIstek}, basari sayilan hata: ${sessizBasari}`);
# Ayni istemci iki kipe karsi calistirilir; istek sayilari karsilastirilir.
kur() { rm -f kutuphane.db && sqlite3 kutuphane.db < sema.sql; }

kur; node kod-sunucusu.mjs 8477 dogru & a=$!; sleep 0.4
echo "--- dogru kip ---"; node istemci.mjs http://127.0.0.1:8477
kill $a

kur; node kod-sunucusu.mjs 8478 gevsek & b=$!; sleep 0.4
echo "--- gevsek kip ---"; node istemci.mjs http://127.0.0.1:8478
kill $b
dogru kip 127.0.0.1:8477
--- dogru kip ---
oku       200  basari  deneme: 1
yok       404  hata    deneme: 1
olustur   201  basari  deneme: 1  location: /oduncler/4
sinir     409  hata    deneme: 1
isbn      422  hata    deneme: 1
bozuk     400  hata    deneme: 1
tur       415  hata    deneme: 1
sil       204  basari  deneme: 1
yontem    405  hata    deneme: 1
toplam istek: 9, basari sayilan hata: 0
gevsek kip 127.0.0.1:8478
--- gevsek kip ---
oku       200  basari  deneme: 1
yok       200  basari  deneme: 1
olustur   200  basari  deneme: 1
sinir     500  hata    deneme: 4
isbn      500  hata    deneme: 4
bozuk     500  hata    deneme: 4
tur       500  hata    deneme: 4
sil       200  basari  deneme: 1
yontem    500  hata    deneme: 4
toplam istek: 24, basari sayilan hata: 1

Ölçümün Okunması

Dokuz senaryo, doğru eşlemede dokuz istekle bitti; gevşek eşlemede yirmi dört istek gerekti. Fark, dört senaryonun yeniden denenmiş olmasından gelir. Bu dört senaryonun ortak yanı şudur: hiçbiri tekrarlanınca düzelmez. Üye sınırı doludur, ISBN katalogda yoktur, gövde bozuktur, içerik türü yanlıştır. Sunucu bunları 500 ile bildirdiğinde istemciye “bende geçici bir sorun var, tekrar dene” demiş olur. İstemci sözü tutar ve üç kez daha dener. İstek üçe katlanır, sonuç değişmez.

İkinci sayı daha sinsidir. Gevşek eşlemede bir başarısızlık başarı sayıldı: olmayan bir kitabı isteyen istemci 200 aldı ve gövdedeki hata alanını okumadığı için elinde bir kitap olduğunu sandı. Ekranda boş bir kitap kartı, kütüğe yazılmayan bir hata ve bulunamayan bir kusur bu satırdan doğar.

Üçüncü fark satırlarda değil, eksik olan şeydedir. Doğru eşlemede olustur senaryosu 201 ile birlikte Location başlığını taşıyor; gevşek eşlemede o başlık yok. Yeni kaynağın adresini bildiren tek yer odur. Onsuz istemci, oluşturduğu ödünç kaydına ulaşmak için ya gövdedeki kimliği okuyup adresi kendisi kurmak ya da koleksiyonu yeniden çekmek zorunda kalır.

Yakın Kodların Ayrımı

Kod seçimini yapan soru şudur: isteğin hangi bölümü kabul edilmedi?

  • 400 — Gövde okunamadı. İstek, biçim düzeyinde bozuktur; JSON çözümlenemedi, zorunlu bir başlık eksik. Sunucu isteği anlamlandıramadığı için içeriğe dair bir şey söyleyemez.
  • 415 — Gövde okunabilir olabilir ama içerik türü desteklenmiyor. Sorun içerikte değil, içeriğin hangi biçimde gönderildiğindedir.
  • 422 — Gövde çözümlendi, alanlar okundu, ama değerler kabul edilmedi. Katalogda olmayan bir ISBN böyledir: yazım doğru, gönderilen değer geçersiz. Bu ayrımın pratik değeri, istemcinin hatayı forma geri bağlayabilmesidir — hangi alanın reddedildiği söylenebilir. Alan düzeyinde hata bildirimi ayrı bir dersin konusudur.
  • 409 — Gövde de değerler de geçerli, ama sistemin o anki durumu isteğin yerine getirilmesine izin vermiyor. Üye sınırının dolu olması budur. 422 ile 409 arasındaki fark zamandır: 422 aynı gövde için hep geçersizdir, 409 durum değişince geçerli olabilir.
  • 404 — Adreslenen kaynak yok. Sorun gövdede değil yoldadır.
  • 405 — Kaynak var, yöntem uygulanamıyor. Yanıt, hangi yöntemlerin kabul edildiğini Allow başlığıyla bildirir; yukarıdaki sunucu Allow: GET gönderir. 404 ile karıştırmak istemciyi yanlış yere bakmaya iter: adresi düzeltmeye çalışır, oysa yöntemi düzeltmesi gerekir.

Kimlik ve yetki kodları — 401 ile 403 — bu listeye bilinçli olarak alınmadı. İkisi arasındaki ayrım kimliğin bilinip bilinmediğine dayanır ve Kimlik Doğrulama ve Yetkilendirme kursunda ele alınır. Buradaki tek kural, ikisinin de 404’e indirgenmemesidir.

2xx Ailesi

Başarı da tek bir kod değildir.

200 genel başarıdır ve gövde taşır. 201 yeni bir kaynak oluşturulduğunu söyler; yanında Location başlığı bulunmalıdır, çünkü istemci o adresi kendisi kuramaz. 204 başarıyı gövdesiz bildirir; silme işlemlerinde ve döndürülecek yeni bir gösterim olmadığında kullanılır. 202 isteğin kabul edildiğini ama henüz tamamlanmadığını söyler; işin sonucunu izlemek için ayrı bir kaynak gerektirir ve eşzamansız işleme konusuna girer.

Seçimin ölçütü yine istemcinin kararıdır. 204 alan bir istemci gövdeyi okumaya çalışmaz; 200 alan istemci okur. Silme yanıtına boş bir gövdeyle 200 dönmek, istemciyi olmayan bir gövdeyi çözümlemeye zorlar ve gövde hatası üretir.

Bilinmeyeni 500’e Toplamak

Gevşek kipin asıl kusuru 500 kodunu kullanması değil, her şey için kullanmasıdır. 500, sunucunun kendi kusurunu bildirdiği koddur: beklenmeyen bir özel durum, erişilemeyen bir veritabanı, çözülmemiş bir varsayım. Bu kodu istemcinin hatası için kullanmak iki şeyi aynı anda bozar. İstemci, düzeltebileceği bir sorunu düzeltmez — çünkü kendisine sorunun onda olmadığı söylenmiştir. Sunucu tarafında ise gerçek kusurların sayısı, istemci hatalarıyla karışır ve ölçülemez hâle gelir.

Doğru davranış, bilinen her başarısızlığı kendi koduna eşlemek ve 500’ü yalnızca sınıflandırılamayan durum için saklamaktır. Sınıflandırılamayan durumun gövdesinde de ayrıntı verilmez; iç hata iletisini dışarıya yazmak, sunucunun iç yapısını sözleşmeye sızdırır.

Özet

  • Durum kodu, yanıtın istemci tarafından yorumlanmadan okunan bölümüdür; önbellek, istek katmanı ve yeniden deneme düzeneği kararlarını ona bakarak verir.
  • Aynı dokuz senaryo doğru eşlemede dokuz istekle bitti, gevşek eşlemede yirmi dört istek üretti; fark, düzelmeyecek hataların 5xx ile bildirilip yeniden denenmesinden doğar.
  • Gevşek eşlemede bir başarısızlık başarı sayıldı: 200 ile dönen “bulunamadı”, istemcinin hiç fark etmediği bir hataya dönüştü.
  • Hata kodu seçimi isteğin hangi bölümünün kabul edilmediğine bakar: 400 biçim, 415 içerik türü, 422 alan değerleri, 409 sistemin o anki durumu, 404 adres, 405 yöntem.
  • 201 yanıtı Location başlığı olmadan eksiktir; 204 gövdesiz başarıyı bildirir ve istemciyi gövde çözümlemeye zorlamaz.
  • 500 yalnızca sunucunun sınıflandıramadığı kusur için ayrılır; istemci hataları için kullanıldığında hem istemci yanlış yönlendirilir hem de gerçek kusurlar ölçülemez olur.

Sonraki Adım

Kodlar yerine oturdu, ama yanıtların içine ne konacağı hâlâ açık. Bu derste gövdeler gelişigüzel yazıldı: bir yerde yalnız bir kimlik alanı, başka bir yerde hata adı ve sınır değeri. Alan adları hangi yazım biçiminde olacak, tarihler nasıl yazılacak, boş değerle eksik alan aynı şey mi sayılacak, koleksiyon yanıtı doğrudan bir dizi mi olacak? Sonraki ders gövde biçimini ele alır ve yanlış seçimlerin nerede sessizce veri bozduğunu — büyük sayıların çözümlenmesinde ve tarih yazımında — çalıştırarak 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