Ders 06 / 34
Eski Nesil Protokoller
Zarf tabanlı protokollerin tasarımı ve mirası: başlık ile gövdenin ayrılması, işlem adının zarfın içinde taşınması, hatanın taşıma katmanı yerine zarfta bildirilmesi, zarf maliyetinin kayıt sayısıyla değişimi ve bugünkü arayüz kararlarının hangilerinin buradan devralındığı.
İçindekiler
Önceki ders tek uç nokta üzerinden, gövdede taşınan bir seçim belgesiyle çalışan bir arayüz kurdu. Bu düzenek yeni değildir. Aynı fikri — tek adres, tek yöntem, işlemi gövdede adlandırma — çok daha önce kuran bir protokol ailesi vardır ve bugünkü arayüz kararlarının bir kısmı doğrudan oradan devralınmıştır.
Bu ailenin ortak yapı taşı zarftır (envelope): iletinin tamamını saran, içinde bir
başlık ve bir gövde bulunduran bir kap. SOAP bu yapının en yaygın bilinen örneğidir
ve XML üzerine kurulmuştur; Envelope, Header, Body ve Fault ögeleri bu dersin
kuracağı Zarf, Baslik, Govde ve Hata ögelerinin karşılığıdır.
Zarfın Fikri
Zarfın çözdüğü sorun şudur: bir iletiyi taşıyan protokolden bağımsız olarak, iletinin kendi üstverisini yanında taşımak. HTTP başlıkları bu işi zaten yapar, ama zarf tasarımı HTTP’ye bağlı değildir — aynı zarf bir ileti kuyruğu ya da bir posta iletisi üzerinden de gidebilir. Bunun bedeli ve kazancı bir arada durur.
Zarfın üç yapısal kararı vardır:
- Başlık kesişen ilgileri taşır: izlem kimliği, kimlik bilgisi, işlem adı. Ara katmanlar bu bölümü okuyup gövdeyi hiç çözümlemeden karar verebilir.
- Gövde yükü taşır: işleme özgü veri. Yükün biçimi işlemden işleme değişir.
- Hata da gövdeye girer: başarısızlık, taşıma katmanının durum koduyla değil, gövdedeki bir hata ögesiyle bildirilir.
Küçük Bir Gerçekleştirim
Aşağıdaki dosya zarfı kuran ve okuyan işlevleri içerir. XML ayrıştırıcısı iç içe ögeleri bir nesneye çevirir; öznitelik ve ad alanı desteklemez, çünkü ders için gereken şey yapının kendisidir.
// zarf.mjs — kucuk XML ayristirici ve zarf kuruculari. // Zarf/Baslik/Govde/Hata ogeleri SOAP'taki Envelope/Header/Body/Fault karsiliklaridir. const kacisla = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">"); const cozKacis = (s) => s.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&"); export const xmlYaz = (ad, icerik) => typeof icerik === "object" && icerik !== null ? `<${ad}>${Object.entries(icerik).map(([a, d]) => xmlYaz(a, d)).join("")}</${ad}>` : `<${ad}>${kacisla(icerik)}</${ad}>`; // Ayristirici: ic ice ogeleri bir nesneye cevirir. Ayni adli kardesler diziye toplanir. export const xmlOku = (metin) => { const oge = /<(\w+)>([\s\S]*?)<\/\1>/g; const cikti = {}; let e, bulundu = false; while ((e = oge.exec(metin))) { bulundu = true; const deger = /<\w+>/.test(e[2]) ? xmlOku(e[2]) : cozKacis(e[2]); if (e[1] in cikti) cikti[e[1]] = [].concat(cikti[e[1]], deger); else cikti[e[1]] = deger; } return bulundu ? cikti : cozKacis(metin); }; export const zarfla = (islem, izlem, govde) => `<Zarf>${xmlYaz("Baslik", { Islem: islem, Izlem: izlem })}` + `<Govde>${Object.entries(govde).map(([a, d]) => xmlYaz(a, d)).join("")}</Govde></Zarf>`; export const hataZarfi = (kod, neden, ayrinti) => `<Zarf><Baslik><Islem>Hata</Islem></Baslik><Govde>` + xmlYaz("Hata", { Kod: kod, Neden: neden, Ayrinti: ayrinti }) + `</Govde></Zarf>`; // Ara katmanin okudugu tek yer: govdeyi hic cozumlemeden baslik alinabilir. export const basligiAyikla = (zarf) => xmlOku(/<Baslik>[\s\S]*?<\/Baslik>/.exec(zarf)[0]).Baslik;
// sunucu.mjs — tek uc nokta, tek yontem: islem adi zarfin basligindadir. import { createServer } from "node:http"; import { xmlOku, zarfla, hataZarfi, basligiAyikla } from "./zarf.mjs"; const KITAPLAR = { "978-0262033848": { isbn: "978-0262033848", ad: "Algoritmalara Giris", yazar: "Cormen", sayfa: 1312 }, "978-0201896831": { isbn: "978-0201896831", ad: "Programlama Sanati", yazar: "Knuth", sayfa: 650 }, }; createServer(async (istek, yanit) => { yanit.sendDate = false; let ham = ""; for await (const p of istek) ham += p; const baslik = basligiAyikla(ham); // yonlendirme: yalniz baslik okunur const govde = xmlOku(ham).Zarf.Govde; let cikti; if (baslik.Islem === "KitapGetir") { const kitap = KITAPLAR[govde.KitapIstek.isbn]; cikti = kitap ? zarfla("KitapGetirYanit", baslik.Izlem, { Kitap: kitap }) : hataZarfi("Istemci", "kitap bulunamadi", `isbn: ${govde.KitapIstek.isbn}`); } else cikti = hataZarfi("Istemci", "bilinmeyen islem", baslik.Islem); yanit.setHeader("Content-Type", "text/xml; charset=utf-8"); yanit.setHeader("Content-Length", Buffer.byteLength(cikti)); // Hata da basarili istek de ayni durum koduyla doner: hata tasima katmaninda degil zarftadir. yanit.writeHead(200).end(cikti); }).listen(8481, "127.0.0.1", () => console.log("zarf ucu 127.0.0.1:8481/servis"));
Ölçüm
// olc.mjs — zarf tabanli protokolun uc ozelligini olcer import { zarfla, xmlOku, basligiAyikla } from "./zarf.mjs"; const cagir = async (islem, izlem, govde) => { const istek = zarfla(islem, izlem, govde); const y = await fetch("http://127.0.0.1:8481/servis", { method: "POST", headers: { "Content-Type": "text/xml" }, body: istek }); const metin = await y.text(); return { kod: y.status, gonderilen: Buffer.byteLength(istek), alinan: Buffer.byteLength(metin), metin, cozulen: xmlOku(metin).Zarf.Govde }; }; console.log("-- 1) tek uc nokta, islem adi zarfta --"); const iyi = await cagir("KitapGetir", "izl-1", { KitapIstek: { isbn: "978-0262033848" } }); console.log(` istek : ${zarfla("KitapGetir", "izl-1", { KitapIstek: { isbn: "978-0262033848" } })}`); console.log(` yanit : ${iyi.metin}`); console.log(` HTTP ${iyi.kod} gonderilen=${iyi.gonderilen} B alinan=${iyi.alinan} B`); console.log("\n-- 2) zarf maliyeti: ayni veri JSON ve zarf biciminde --"); const KAYIT = { isbn: "978-0262033848", ad: "Algoritmalara Giris", yazar: "Cormen", sayfa: 1312 }; for (const adet of [1, 5, 20]) { const liste = Array.from({ length: adet }, () => KAYIT); const json = Buffer.byteLength(JSON.stringify({ Kitap: liste })); const zarf = Buffer.byteLength(zarfla("KitapGetirYanit", "izl-1", { Kitap: liste })); console.log(` ${String(adet).padStart(2)} kayit json=${String(json).padStart(4)} B ` + `zarf=${String(zarf).padStart(4)} B fark=%${Math.round(100 * (zarf / json - 1))}`); } console.log("\n-- 3) hata zarfta, tasima katmaninda degil --"); const kotu = await cagir("KitapGetir", "izl-2", { KitapIstek: { isbn: "978-0000000000" } }); console.log(` yanit : ${kotu.metin}`); console.log(` durum koduna bakan istemci -> HTTP ${kotu.kod} : "istek basarili"`); console.log(` govdeyi cozen istemci -> ${JSON.stringify(kotu.cozulen.Hata)}`); console.log("\n-- 4) ara katman govdeyi cozmeden basligi okur --"); for (const z of [iyi.metin, kotu.metin]) { const govdeBoyu = /<Govde>[\s\S]*<\/Govde>/.exec(z)[0].length; console.log(` baslik=${JSON.stringify(basligiAyikla(z))} (govde ${govdeBoyu} B, cozumlenmedi)`); }
node sunucu.mjs & p=$! curl -s --retry 20 --retry-connrefused --retry-delay 0 -o /dev/null -X POST \ -d '<Zarf><Baslik><Islem>x</Islem></Baslik><Govde></Govde></Zarf>' http://127.0.0.1:8481/servis node olc.mjs kill $p
zarf ucu 127.0.0.1:8481/servis
-- 1) tek uc nokta, islem adi zarfta --
istek : <Zarf><Baslik><Islem>KitapGetir</Islem><Izlem>izl-1</Izlem></Baslik><Govde><KitapIstek><isbn>978-0262033848</isbn></KitapIstek></Govde></Zarf>
yanit : <Zarf><Baslik><Islem>KitapGetirYanit</Islem><Izlem>izl-1</Izlem></Baslik><Govde><Kitap><isbn>978-0262033848</isbn><ad>Algoritmalara Giris</ad><yazar>Cormen</yazar><sayfa>1312</sayfa></Kitap></Govde></Zarf>
HTTP 200 gonderilen=142 B alinan=205 B
-- 2) zarf maliyeti: ayni veri JSON ve zarf biciminde --
1 kayit json= 94 B zarf= 212 B fark=%126
5 kayit json= 426 B zarf= 620 B fark=%46
20 kayit json=1671 B zarf=2170 B fark=%30
-- 3) hata zarfta, tasima katmaninda degil --
yanit : <Zarf><Baslik><Islem>Hata</Islem></Baslik><Govde><Hata><Kod>Istemci</Kod><Neden>kitap bulunamadi</Neden><Ayrinti>isbn: 978-0000000000</Ayrinti></Hata></Govde></Zarf>
durum koduna bakan istemci -> HTTP 200 : "istek basarili"
govdeyi cozen istemci -> {"Kod":"Istemci","Neden":"kitap bulunamadi","Ayrinti":"isbn: 978-0000000000"}
-- 4) ara katman govdeyi cozmeden basligi okur --
baslik={"Islem":"KitapGetirYanit","Izlem":"izl-1"} (govde 125 B, cozumlenmedi)
baslik={"Islem":"Hata"} (govde 116 B, cozumlenmedi)
Ölçümün Söyledikleri
Zarfın maliyeti sabittir, yükün maliyeti değişkendir. Tek kayıtta zarf, aynı verinin JSON biçiminden %126 daha büyüktür; yirmi kayıtta bu fark %30’a iner. Zarfın kendisi — başlık bölümü, sarmalayan ögeler — kayıt sayısından bağımsız sabit bir maliyettir; kayıt başına düşen fazlalık ise kapanış etiketlerinden gelir ve her kayıtta tekrarlanır. Küçük ve sık çağrılarda zarf pahalıdır; büyük yüklerde oran düşer.
Hatanın zarfta bildirilmesinin bedeli tüketiciye çıkar. Var olmayan bir kitap istendiğinde
sunucu 200 döndürmüştür. Durum koduna bakan bir istemci isteği başarılı sayar; hatayı
görmek için gövdeyi çözümlemek gerekir. Bu, dördüncü derste anılan hata sözleşmesinin ikiye
bölünmesi sorununun kaynağıdır ve GraphQL’in kısmen başarılı yanıtında aynı biçimde geri
gelir.
Başlık gövdeden bağımsız okunabilir. Ölçümün son bölümü, yüz baytın üzerindeki gövde hiç çözümlenmeden başlığın alınabildiğini gösterir. Bu, bir ara katmanın — yönlendirici, yetkilendirme katmanı, izleme katmanı — yükü anlamadan iş yapabilmesi demektir. Başlığın taşıdığı izlem kimliği aynı isteğin farklı bileşenlerdeki kayıtlarını birbirine bağlar.
Bırakılan Miras
Zarf tabanlı tasarımın bugüne aktarılan yanları, ölçümde görülen özelliklerin doğrudan devamıdır.
Makine okunur sözleşme. Bu ailenin arayüzleri, hangi işlemlerin bulunduğunu ve her işlemin girdi–çıktı tiplerini ayrı bir tanım dosyasında yayımlar. Buradan iki alışkanlık kaldı: sözleşmenin belgeden ayrı, çalıştırılabilir bir dosya olması ve istemci tarafının bu dosyadan üretilebilmesi. Aynı fikir dördüncü derste şema dosyası olarak, bu kursun ilerleyen bölümünde ise OpenAPI ve JSON Schema tabanlı tanımlar olarak görülür.
Yapılandırılmış hata. Hata ögesindeki üçlü — kod, neden, ayrıntı — bugünkü standart hata gövdelerinin atasıdır: makinenin okuyacağı bir kod, insanın okuyacağı bir açıklama ve bağlama özgü bir ek. Bu yapı, hata sözleşmesi dersinde ayrıntılandırılır.
Kesişen ilgilerin ayrılması. Kimlik, izlem ve yönlendirme bilgisinin yükten ayrı tutulması korunmuştur; HTTP tabanlı arayüzlerde bu ayrım zarfın başlığı yerine HTTP başlıklarında yaşar.
İşlem adının gövdede taşınması. Tek uç nokta ve gövdede işlem adı örüntüsü, hem uzak yordam çağrısı biçemli HTTP arayüzlerinde hem de GraphQL’de sürer.
Devralınmayanlar da bellidir. Taşımadan bağımsız olma isteği, HTTP’nin kendi anlamlarını —
yöntemler, durum kodları, koşullu istekler, önbellek yönergeleri — kullanılmaz kılar. Üçüncü
derste ölçülen önbelleklenebilirlik kazancı, her isteğin POST olduğu bir tasarımda elde
edilemez. Metin biçiminin ayrıntı yüküyle birlikte bu, aynı işi daha az bayt ve daha az
katmanla yapan tasarımların yolunu açan asıl nedendir.
Özet
- Zarf tabanlı protokoller iletiyi başlık ve gövde olarak saran bir kapla çalışır; işlem adı adreste değil, zarfın başlığında taşınır ve taşıma katmanı değiştirilebilir sayılır.
- Ölçümde zarfın bayt maliyeti tek kayıtta %126, yirmi kayıtta %30 çıktı: zarfın kendisi sabit maliyet, kapanış etiketleri ise kayıt başına tekrarlanan maliyettir.
- Hata zarfın içinde bildirildiği için sunucu başarısız işlemde de
200döndürür; durum koduna bakan istemci hatayı göremez, gövdeyi çözümlemek zorundadır. - Başlık gövdeden bağımsız okunabildiği için ara katmanlar yükü hiç çözümlemeden yönlendirme, yetkilendirme ve izleme yapabilir.
- Devralınan miras dört kalemdir: makine okunur sözleşme dosyası, yapılandırılmış hata yapısı, kesişen ilgilerin yükten ayrılması ve tek uç nokta üzerinde adlandırılmış işlem.
Sonraki Adım
Buraya kadarki beş biçemin ortak varsayımı aynıdır: iletişimi istemci başlatır, sunucu yanıt verir, alışveriş biter. Kütüphane servisinin bazı işleri bu kalıba girmez. Bir kitabın rafa dönmesi sunucuda olan bir olaydır ve bunu bekleyen istemciye kimse sormaz. Bir sayım işi dakikalarca sürer ve isteği açık tutmak bir seçenek değildir. Sonraki ders istek–yanıt dışındaki iletişim biçimlerini kurar ve ikisini ölçerek karşılaştırır: bir olay akışı ile uzun yoklama aynı bildirimi kaç bağlantıyla ve ne kadar gecikmeyle ulaştırır?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.