Ders 12 / 34
İstek ve Yanıt Gövdeleri
Gövde biçiminin sözleşme değeri: alan adlandırmada tek yazım, büyük tamsayıların ve ondalık tutarların sessiz kaybı, tarihlerin bölge bilgisiyle yazılması, eksik alan ile boş değerin ayrılması ve koleksiyon yanıtının zarf kararı.
İçindekiler
Bir önceki ders yanıtın makine tarafından okunan bölümünü — durum kodunu — yerine oturttu. Geriye gövde kaldı. Ölçüm sunucularında gövdeler gelişigüzel yazıldı: bir yerde yalnız bir kimlik alanı, başka yerde hata adı ve sınır değeri, bir başka yerde tablonun sütun adları olduğu gibi.
Gövde biçimi, durum kodu kadar sözleşmedir. Alan adı değişirse istemci kırılır; bir sayının nasıl yazıldığı yanlış seçilirse istemci kırılmaz — daha kötüsü olur, sessizce yanlış değer okur. Bu ders gövde kararlarını sıralar ve ikisi ölçülerek gösterilir: sayıların ve tarihlerin çözümlenmesi, bir de eksik alanla boş değerin ayrılması.
Gövde Bir Sözleşmedir
İlk karar alan adlandırmasıdır ve içeriğinden çok tutarlılığı önemlidir. Bir yanıtta
verilis, başka bir yanıtta verilisTarihi, üçüncüsünde verilis_tarihi geçiyorsa
istemci her uç nokta için ayrı bir çözümleme yazar. Kurs boyunca tek bir yazım seçilir ve
değişmez.
İkinci karar, gövdenin veritabanı şemasının kopyası olmamasıdır. Bir önceki derste ödünç
kaydı doğrudan SELECT * sonucuyla döndürülmüştü; bu, sütun adlarını sözleşmeye
sızdırır. Sütun adı değiştiğinde ya da bir sütun eklendiğinde istemci etkilenir. Doğru
davranış, gösterimi açıkça kurmaktır: hangi alanların dışarı çıkacağı yazılır, geri kalanı
içeride kalır.
Üçüncü karar numaralandırma değerlerine ilişkindir. Bir ödünç kaydının durumu acik,
iade_edildi, gecikti gibi sabit kodlarla bildirilir; “Açık”, “İade Edildi” gibi
görüntülenecek metinlerle değil. Metin dile ve yazıma bağlıdır, kod ise sözleşmenin
parçasıdır ve karşılaştırılabilir. Ekranda ne yazacağı istemcinin kararıdır.
Sayılar: Sessiz Kayıp
Kütüphane katalogunda her kopyanın bir demirbaş numarası vardır ve bu numara başka bir sistemden gelir. Gecikme ücreti de ondalıklı bir tutardır. İkisi de JSON’da sayı olarak yazıldığında ne olduğuna bakalım.
# JSON govdesindeki iki sessiz kayip: buyuk tamsayi ve bicimsiz tarih. node -e ' const govde = `{"demirbas": 9007199254740993, "ucret": 0.1, "ek": 0.2}`; const c = JSON.parse(govde); console.log("gonderilen demirbas :", govde.match(/\d{16}/)[0]); console.log("cozulen demirbas :", String(c.demirbas)); console.log("esit mi :", String(c.demirbas) === govde.match(/\d{16}/)[0]); console.log("ucret + ek :", c.ucret + c.ek); ' echo "---" TZ=Europe/Istanbul node -e ' for (const d of ["12/03/2026", "2026-03-12", "2026-03-12T00:00:00", "2026-03-12T00:00:00+03:00"]) console.log(d.padEnd(28), new Date(d).toISOString()); '
gonderilen demirbas : 9007199254740993 cozulen demirbas : 9007199254740992 esit mi : false ucret + ek : 0.30000000000000004 --- 12/03/2026 2026-12-02T21:00:00.000Z 2026-03-12 2026-03-12T00:00:00.000Z 2026-03-12T00:00:00 2026-03-11T21:00:00.000Z 2026-03-12T00:00:00+03:00 2026-03-11T21:00:00.000Z
İlk üç satır bir kimliğin çözümleme sırasında bozulduğunu gösteriyor. JSON’un sayı tipinin
kesinliği çözümleyicinin kayan noktalı sayı temsiline bağlıdır; Bilgisayarlar Nasıl Çalışır
kursunda tanımlanan çift duyarlıklı temsilin tamsayı sınırı aşıldığında en yakın
temsil edilebilir değere yuvarlanır. Sunucuya ...993 gönderilir, istemcide ...992
okunur. Hiçbir hata üretilmez; kayıp, o kimlikle yapılan bir sorgu boş dönene kadar
görünmez.
Dördüncü satır aynı kökten gelen ikinci sorundur: 0.1 + 0.2 işleminin sonucu tam
0.3 değildir. Para tutarları ondalıklı sayı olarak taşındığında toplama işlemleri
biriken hatalar üretir.
Her iki sorunun da çözümü aynıdır: hesaplanmayacak sayılar dizgi olarak taşınır. Kimlikler dizgidir — zaten üzerlerinde aritmetik yapılmaz. Para tutarları ya en küçük birim cinsinden tamsayı olarak (kuruş) ya da dizgi olarak taşınır ve karşılaştırma o biçimde yapılır. Bir kimliğin sayı görünümlü olması onu sayı yapmaz.
Tarihler: Biçimsiz Dizgi Yoktur
Çıktının ikinci bölümü tarih alanlarını gösteriyor ve dört satırın dördü de farklı bir
tuzağa denk gelir. Bu blok TZ=Europe/Istanbul ile çalıştırılmıştır; yerel saat dilimi
değiştiğinde üçüncü satırın sonucu da değişir — asıl sorun da budur.
12/03/2026 yazımı 12 Mart olarak değil, 2 Aralık olarak çözümlendi. Gün ve ayın hangi
sırada yazıldığı yazımın kendisinden anlaşılmadığı için çözümleyici kendi varsayımını
uyguladı. Sözleşmede böyle bir alan bulunuyorsa istemcilerin yarısı sekiz ay ileriyi okur.
2026-03-12 yazımı bölgesiz bir gündür ve gün ortası UTC olarak alındı.
2026-03-12T00:00:00 yazımı ise bölge bilgisi taşımadığı için yerel saat sayıldı ve
UTC’ye çevrildiğinde bir gün geriye kaydı. Aynı dizgi, iki farklı makinede iki farklı ana
karşılık gelir. Yalnızca son satır — bölge farkını açıkça yazan biçim — her yerde aynı anı
gösterir.
Kural şudur: bir zaman anı bildiriliyorsa bölge farkı yazılır. Ödünç verme anı gibi alanlar tam biçimde taşınır. Yalnızca takvim günü bildiriliyorsa — son iade günü gibi — alanın adı bunu söylemeli ve gösterimde saat hiç bulunmamalıdır. İki türü aynı alanda karıştırmak, yaz saati geçişlerinde bir günlük kaymalar üretir.
Yok ile Boş Aynı Şey Değil
Kısmi güncellemede istemci üç şey söyleyebilir: bu alanı şu değere getir, bu alanı boşalt, bu alana dokunma. JSON gövdesinde ilk ikisi değerle, üçüncüsü alanın hiç bulunmamasıyla bildirilir. Bu ayrımı gözetmeyen bir yazım, “boşalt” isteğini hiçbir zaman gerçekleştiremez.
// alan-sunucusu.mjs — kismi guncellemede "yok" ile "bos" ayrimi import { createServer } from "node:http"; import { DatabaseSync } from "node:sqlite"; 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 ? JSON.parse(veri) : {})); }); const yanitla = (yanit, kod, nesne) => { yanit.writeHead(kod, { "content-type": "application/json; charset=utf-8" }); yanit.end(JSON.stringify(nesne)); }; const sunucu = createServer(async (istek, yanit) => { const yol = new URL(istek.url, "http://127.0.0.1").pathname; const gevsek = /^\/gevsek\/oduncler\/(\d+)$/.exec(yol); const kesin = /^\/kesin\/oduncler\/(\d+)$/.exec(yol); if (istek.method !== "PATCH" || !(gevsek || kesin)) return yanitla(yanit, 404, { hata: "yol_yok" }); const id = Number((gevsek ?? kesin)[1]); const g = await govdeOku(istek); const mevcut = db.prepare("SELECT * FROM odunc WHERE id = ?").get(id); if (!mevcut) return yanitla(yanit, 404, { hata: "odunc_yok" }); if (gevsek) { // Bos deger ile eksik alani ayirmayan yazim: null hicbir zaman yazilamaz. const yeni = g.iade ?? mevcut.iade; db.prepare("UPDATE odunc SET iade = ? WHERE id = ?").run(yeni, id); } else { // Alanin govdede bulunup bulunmadigina bakilir; degeri ayrica degerlendirilir. if ("iade" in g) db.prepare("UPDATE odunc SET iade = ? WHERE id = ?").run(g.iade, id); } yanitla(yanit, 200, db.prepare("SELECT id, iade FROM odunc WHERE id = ?").get(id)); }); sunucu.listen(8479, "127.0.0.1", () => console.log("alan sunucusu 127.0.0.1:8479"));
# Ayni uc govde iki yazima gonderilir; iade alaninin son degeri karsilastirilir. rm -f kutuphane.db && sqlite3 kutuphane.db < sema.sql node alan-sunucusu.mjs & sunucu=$! sleep 0.4 yolla() { curl -sS -X PATCH -H 'content-type: application/json' -d "$2" "http://127.0.0.1:8479$1"; echo; } for kip in gevsek kesin; do echo "--- $kip ---" yolla "/$kip/oduncler/2" '{"iade":"2026-03-20"}' yolla "/$kip/oduncler/2" '{"uye":"U-1001"}' yolla "/$kip/oduncler/2" '{"iade":null}' done kill $sunucu
alan sunucusu 127.0.0.1:8479
--- gevsek ---
{"id":2,"iade":"2026-03-20"}
{"id":2,"iade":"2026-03-20"}
{"id":2,"iade":"2026-03-20"}
--- kesin ---
{"id":2,"iade":"2026-03-20"}
{"id":2,"iade":"2026-03-20"}
{"id":2,"iade":null}
Her iki kipin ilk iki satırı aynı: değer atanır, alan gövdede yoksa korunur. Üçüncü satır
ayrışır. Gevşek yazımda iade alanı boşaltılamadı; çünkü ?? işleci null değeri
“verilmemiş” sayar ve eski değeri geri koyar. Yanlışlıkla kapatılmış bir ödünç kaydını
düzeltmenin yolu yoktur; istemci isteği gönderir, 200 alır, hiçbir şey değişmez.
Kesin yazımda ayrım gövdenin yapısından okunur: alan gövdede varsa değeri — null dahil —
uygulanır, yoksa dokunulmaz. Bu davranış sözleşmede yazılı olmalıdır, çünkü istemcinin
“alanı göndermemek” ile “alanı boş göndermek” arasındaki farkı bilerek kullanması gerekir.
Aynı ayrım yanıt tarafında da geçerlidir. Bir alanın değeri yoksa null olarak
gönderilmesi ile hiç gönderilmemesi farklı şeyler söyler: birincisi “bu alan var ama boş”,
ikincisi “bu alan bu gösterimde yok”. Kurs boyunca tek bir kural seçilir; yanıtta bazen
null, bazen alanın hiç bulunmaması istemciyi her iki olasılığı da yazmaya zorlar.
Koleksiyon Yanıtı: Çıplak Dizi mi, Zarf mı
Koleksiyon yanıtının en kısa biçimi çıplak bir dizidir. Kısa olması tek üstünlüğüdür. Diziye ek bilgi konamaz: kaç kayıt olduğu, bir sonraki sayfanın nerede başladığı, sorgunun hangi ölçütle çalıştığı. Bu bilgiler başlıklara taşınabilir, ama başlıklar gövdeyle birlikte önbelleklenmeyebilir ve okunmaları istemci tarafında ayrı bir iş gerektirir.
Zarf yazımı gövdeyi bir nesneye sarar: kayıtlar bir alanda, sayfalama bilgisi başka bir alanda durur. Maliyeti bir düzey fazladan iç içelik, kazancı yanıtın kendini anlatabilmesidir. Sonraki iki ders — sayfalama ve bağlantı odaklı yanıtlar — bu alanı kullanacağı için kurs boyunca zarf yazımı seçilir.
Tekil kaynak yanıtları ise sarılmaz. /oduncler/2 adresinden dönen gövde doğrudan ödünç
kaydının gösterimidir; onu bir alana sarmak, hiçbir bilgi eklemeden her istemciye bir
düzey daha inme yükü getirir.
Özet
- Alan adlandırmada tek yazım biçimi seçilir ve kurs boyunca değişmez; gövde veritabanı şemasının kopyası değil, açıkça kurulmuş bir gösterimdir.
- Üzerinde aritmetik yapılmayacak sayılar — kimlikler — dizgi olarak taşınır; ölçümde
9007199254740993değeri çözümlendiğinde9007199254740992oldu ve hata üretilmedi. - Para tutarları ondalıklı sayı olarak taşınmaz; en küçük birim cinsinden tamsayı ya da dizgi kullanılır.
- Zaman anı bildiren alanlar bölge farkını açıkça yazar; bölgesiz yazımlar makineye göre farklı ana karşılık gelir ve gün/ay sırası belirsiz yazımlar aylarca kayabilir.
- Kısmi güncellemede alanın gövdede bulunup bulunmadığı ile değerinin boş olması ayrı bilgilerdir; ayrımı gözetmeyen yazımda bir alanı boşaltmak olanaksızdır.
- Koleksiyon yanıtları zarfla sarılır, çünkü sayfalama ve gezinme bilgisi gövdenin içinde taşınacaktır; tekil kaynak yanıtları sarılmaz.
Sonraki Adım
Zarfın boş duran alanı bir sonraki dersin konusudur. Ödünç koleksiyonu üç satırla sınırlıyken tüm kayıtları tek yanıtta göndermek sorun değildi; on binlerce kayıtta bu yanıt hem sunucuyu hem istemciyi taşır. Kayıtları parça parça vermek gerekir ve bunun birden çok yolu vardır. Sonraki ders atlama tabanlı sayfalamayı kurar, araya kayıt eklendiğinde okuyucunun bir kaydı iki kez görmesini ya da hiç görmemesini gerçekten üretir, sonra aynı senaryoda kaymayan bir yöntem gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.