Ders 09 / 16
Ters Vekil Yapılandırması
Ters vekilin uygulamaya ne bıraktığı: TLS sonlandırmadan sonra kaybolan istemci adresi ile şema bilgisinin başlık aktarımıyla geri verilmesi, aktarım kapalıyken hız sınırının tek kovaya düşmesi ve denetim kaydının tek adres göstermesi, yolun vekilde bir kez fazla çözülmesiyle yanlış kaydın başarılı olarak dönmesi, gövde boyu sınırının iki katmanda ayrı durmasının etkin değeri ve kaydı nasıl değiştirdiği.
İçindekiler
Önceki konu bütün savunmaları uygulamanın içine kurdu: doğrulayıcı, izin listesi, gizli değer deposu, denetim kaydı, hız sınırlayıcı. Her biri isteği okuyup bir karar veriyordu ve hepsi tek bir varsayıma yaslanıyordu — uygulamanın gördüğü istek, istemcinin gönderdiği istektir.
Üretimde bu varsayım doğru değildir. Uygulama yalnız başına durmaz: önünde bir ters vekil, altında bir süreç yöneticisi vardır. İkisi de isteğe dokunur, ikisi de yapılandırılır ve yapılandırmaları uygulamanın gördüğü isteği değiştirir. Ters vekilin ne olduğu Yük Dengeleyici ve Ters Vekil dersinde kurulmuş, ölçülmüştü; bu ders bileşeni yeniden tanımlamaz, ayarlarını ölçer.
Sonlandırmanın Bıraktığı Boşluk
TLS sonlandırma vekilde yapılır: şifreli bağlantı vekilde biter, vekil ile uygulama arasındaki bağlantı şifresiz taşınır. Bu düzenek uygulamadan iki bilgiyi alır. Birincisi istemci adresi: uygulamanın gördüğü bağlantının öteki ucu artık istemci değil, vekildir. İkincisi şema: uygulamaya gelen bağlantı şifresizdir, isteğin dışarıda şifreli gelip gelmediği bağlantıda yazmaz.
İki bilgi de kaybolmaz, aktarılır — vekil isteğe iki başlık ekler. Aktarım bir ayardır ve kapalıyken uygulama hata vermez; yalnız yanlış bir değer okur.
- DP1. Tek makinede bütün istemciler aynı geri döngü adresini paylaşır. Üye adresi vekilde kaynak bağlantı noktası bloğundan türetilir; gerçek dağıtımda bu değer doğrudan bağlantıdan gelir.
- DP2. Ders şifrelemenin kendisini değil, sonlandırmanın uygulamaya bıraktığı bilgi kaybını ölçer.
- DP3. Hız sınırı kimlik başına beş istektir ve pencere tek koşumdur; denetim kaydı isteği çözülen kimliğe yazar.
Ödünç uygulaması kimliği önce başlıktan, yoksa bağlantıdan alır.
// topoloji/uygulama.mjs — odunc uygulamasi: kimligi, semayi ve govde sinirini istekten okur. // Kimlik once "x-iletilen-icin" basligindan, yoksa baglantinin kendisinden alinir. import { createServer } from "node:http"; const [nokta, govdeSiniri] = [Number(process.argv[2]), Number(process.argv[3] ?? 16384)]; const SINIR = 5; // kimlik basina istek const KATALOG = { "KTP/17": "Bolge Tarihi", "KTP%2F17": "Bolge Tarihi Eki" }; const sayac = new Map(); const kayit = { istek: 0, gecen: 0, durdurulan: 0, kimlik: new Set(), okunanBayt: 0 }; function kimlikCoz(istek) { const iletilen = istek.headers["x-iletilen-icin"]; return iletilen ?? istek.socket.remoteAddress; // baslik yoksa: baglantinin oteki ucu } if (Number.isInteger(nokta) === false) console.log("kullanim: node topoloji/uygulama.mjs <nokta> [govde-siniri]"); else createServer((istek, yanit) => { yanit.sendDate = false; const yol = new URL(istek.url, "http://yerel").pathname; if (yol === "/olcum") { yanit.writeHead(200, { "content-type": "application/json" }); yanit.end(JSON.stringify({ ...kayit, kimlik: kayit.kimlik.size })); return; } const kimlik = kimlikCoz(istek); kayit.istek += 1; kayit.kimlik.add(kimlik); const n = (sayac.get(kimlik) ?? 0) + 1; sayac.set(kimlik, n); if (n > SINIR) { kayit.durdurulan += 1; yanit.writeHead(429, { "x-kimlik": kimlik }).end("sinir"); return; } kayit.gecen += 1; if (istek.headers["x-iletilen-sema"] !== "guvenli") { yanit.writeHead(308, { location: yol, "x-kimlik": kimlik }).end("guvenli semaya"); return; } if (yol.startsWith("/kitap/")) { const kimlikKitap = decodeURIComponent(yol.split("/")[2] ?? ""); const ad = KATALOG[kimlikKitap]; if (ad === undefined) yanit.writeHead(404, { "x-kimlik": kimlik }).end("yok"); else yanit.writeHead(200, { "x-kayit": ad, "x-kimlik": kimlik }).end(ad); return; } if (istek.method === "POST") { let bayt = 0; istek.on("data", (p) => { bayt += p.length; kayit.okunanBayt += p.length; if (bayt > govdeSiniri) { yanit.writeHead(413, { "x-katman": "uygulama" }).end("buyuk"); istek.destroy(); } }); istek.on("end", () => { if (yanit.writableEnded === false) yanit.writeHead(200, { "x-kimlik": kimlik }).end(String(bayt)); }); return; } yanit.writeHead(200, { "x-kimlik": kimlik }).end("odunc"); }).listen(nokta, "127.0.0.1");
Vekil dört ayarı bir dizgeden alır: başlık aktarımı, şema aktarımı, yol işleme ve gövde sınırı. Dördü de bir üründe bir yapılandırma satırıdır; burada bir dizge alanıdır.
// topoloji/vekil.mjs — ters vekil. TLS'i kendinde sonlandirir, arkaya sifresiz baglanir. // Ayar dizgesi: basliklar=acik|kapali,sema=acik|kapali,yol=gecir|coz,govde=<bayt> import { createServer, request } from "node:http"; const [nokta, hedef, ayarMetni] = [Number(process.argv[2]), Number(process.argv[3]), process.argv[4] ?? ""]; const A = Object.fromEntries(ayarMetni.split(",").filter(Boolean).map((p) => p.split("="))); const sayac = { gelen: 0, iletilen: 0, kesilen: 0, okunanBayt: 0 }; // DP1: tek makinede butun istemciler ayni geri dongu adresini paylasir. Uye adresi kaynak // baglanti noktasi blogundan turetilir; gercek dagitimda bu deger dogrudan baglantidan gelir. const AG = { 412: "10.4.0.11", 413: "10.4.0.12", 414: "10.4.0.13", 415: "10.4.0.14" }; const istemciAdresi = (s) => AG[Math.floor(s.remotePort / 100)] ?? s.remoteAddress; if (Number.isInteger(nokta) === false || Number.isInteger(hedef) === false) { console.log("kullanim: node topoloji/vekil.mjs <nokta> <hedef> <ayar>"); } else createServer((istekDisi, yanitDisi) => { yanitDisi.sendDate = false; if (istekDisi.url === "/vekilolcum") { yanitDisi.writeHead(200).end(JSON.stringify(sayac)); return; } sayac.gelen += 1; const basliklar = { ...istekDisi.headers }; delete basliklar["x-iletilen-icin"]; // disaridan gelen kimlik iddiasi atilir delete basliklar["x-iletilen-sema"]; if (A.basliklar === "acik") basliklar["x-iletilen-icin"] = istemciAdresi(istekDisi.socket); if (A.sema === "acik") basliklar["x-iletilen-sema"] = "guvenli"; const govdeSiniri = Number(A.govde ?? Infinity); const yol = A.yol === "coz" ? decodeURIComponent(istekDisi.url) : istekDisi.url; let bayt = 0; const istekIc = request({ port: hedef, path: yol, method: istekDisi.method, headers: basliklar }, (yanitIc) => { yanitDisi.writeHead(yanitIc.statusCode, yanitIc.headers); yanitIc.pipe(yanitDisi); }); sayac.iletilen += 1; istekIc.on("error", () => { sayac.kesilen += 1; if (yanitDisi.headersSent === false) yanitDisi.writeHead(502, { "x-katman": "vekil" }).end("arka uc kesildi"); }); istekDisi.on("data", (p) => { bayt += p.length; sayac.okunanBayt += p.length; if (bayt > govdeSiniri) { if (yanitDisi.headersSent === false) yanitDisi.writeHead(413, { "x-katman": "vekil" }).end("buyuk"); istekIc.destroy(); istekDisi.destroy(); return; } istekIc.write(p); }); istekDisi.on("end", () => istekIc.end()); istekDisi.on("error", () => istekIc.destroy()); }).listen(nokta, "127.0.0.1");
Dört üye ayrı kaynak bağlantı noktası bloklarından bağlanır. Üye A on istek gönderir, B, C ve D ikişer istek — toplam on altı istek, kimlik başına sınır beş.
// topoloji/istemci.mjs — uyeler ayri kaynak baglanti noktalarindan baglanir; kimlik baglantida durur. // Senaryolar: hiz | sema | yol | govde. Kullanim: node topoloji/istemci.mjs <nokta> <senaryo> <etiket> import { request } from "node:http"; const [nokta, senaryo, etiket] = [Number(process.argv[2]), process.argv[3], process.argv[4] ?? "-"]; const UYE = [["A", 41200, 10], ["B", 41300, 2], ["C", 41400, 2], ["D", 41500, 2]]; function bir({ yol = "/odunc", kaynak, yontem = "GET", govde }) { return new Promise((coz) => { const r = request({ port: nokta, path: yol, method: yontem, agent: false, localPort: kaynak }, (y) => { y.resume(); y.on("end", () => coz({ kod: y.statusCode, katman: y.headers["x-katman"] ?? "-", kayit: y.headers["x-kayit"] ?? "-", yer: y.headers.location })); }); r.on("error", (e) => coz({ kod: 0, katman: e.code, kayit: "-", yer: undefined })); if (govde !== undefined) r.end(govde); else r.end(); }); } const olcum = async (yol) => JSON.parse(await new Promise((coz) => { request({ port: nokta, path: yol, agent: false }, (y) => { let b = ""; y.on("data", (d) => { b += d; }); y.on("end", () => coz(b)); }).end(); })); if (senaryo === "hiz") { let gecen = 0, durdurulan = 0; const satir = []; for (const [ad, taban, adet] of UYE) { let g = 0, d = 0; for (let i = 0; i < adet; i += 1) { const s = await bir({ kaynak: taban + i }); if (s.kod === 429) d += 1; else g += 1; } gecen += g; durdurulan += d; satir.push(`${ad}:${g}/${g + d}`); } const o = await olcum("/olcum"); console.log(`${etiket.padEnd(24)} kimlik ${String(o.kimlik).padStart(2)} gecen ${String(gecen).padStart(2)} durdurulan ${String(durdurulan).padStart(2)} ${satir.join(" ")}`); } else if (senaryo === "sema") { let adim = 0, kod = 0, yol = "/odunc"; while (adim < 5) { const s = await bir({ yol, kaynak: 41260 + adim }); kod = s.kod; if (s.yer === undefined) break; yol = s.yer; adim += 1; } console.log(`${etiket.padEnd(24)} yonlendirme adimi ${adim}/5 son kod ${kod}`); } else if (senaryo === "yol") { const ISTEK = [["KTP%2F17", "Bolge Tarihi"], ["KTP%252F17", "Bolge Tarihi Eki"]]; const sonuc = []; for (const [i, [kodlu, beklenen]] of ISTEK.entries()) { const s = await bir({ yol: `/kitap/${kodlu}`, kaynak: 41270 + i }); sonuc.push(`${kodlu} -> ${s.kod} ${s.kayit}${s.kod === 200 && s.kayit !== beklenen ? " (yanlis kayit)" : ""}`); } console.log(`${etiket.padEnd(24)} ${sonuc.join(" | ")}`); } else if (senaryo === "govde") { const s = await bir({ yontem: "POST", kaynak: 41280, govde: "x".repeat(8192) }); const v = await olcum("/vekilolcum"); const u = await olcum("/olcum"); console.log(`${etiket.padEnd(24)} istemci ${s.kod} (${s.katman}) vekil okudu ${v.okunanBayt} uygulama okudu ${u.okunanBayt} uygulama kaydi ${u.istek}`); } else console.log("kullanim: node topoloji/istemci.mjs <nokta> <senaryo> <etiket>");
Dört ayar ikişer değerle koşulur. Uygulama her koşumda yeniden başlatılır, yani sayaçlar sıfırdan başlar; değişen tek şey vekilin ayar dizgesidir.
# olc.sh — ayni is dort ayar ikilisiyle. Her kosumda uygulama yeniden baslatilir (sayaclar sifirlanir). kos() { # kos <ayar> <senaryo> <etiket> [uygulama-govde-siniri] node topoloji/uygulama.mjs 8931 "${4:-16384}" & U=$! node topoloji/vekil.mjs 8930 8931 "$1" & V=$! sleep 0.4 node topoloji/istemci.mjs 8930 "$2" "$3" kill $V $U 2>/dev/null; wait $V $U 2>/dev/null; sleep 0.2 } kos "basliklar=kapali,sema=acik" hiz "baslik kapali" kos "basliklar=acik,sema=acik" hiz "baslik acik" kos "basliklar=acik,sema=kapali" sema "sema kapali" kos "basliklar=acik,sema=acik" sema "sema acik" kos "basliklar=acik,sema=acik,yol=gecir" yol "yol gecirildi" kos "basliklar=acik,sema=acik,yol=coz" yol "yol vekilde cozuldu" kos "basliklar=acik,sema=acik,govde=4096" govde "sinir vekilde (4096)" 16384 kos "basliklar=acik,sema=acik,govde=65536" govde "sinir uygulamada (4096)" 4096
baslik kapali kimlik 1 gecen 5 durdurulan 11 A:5/10 B:0/2 C:0/2 D:0/2 baslik acik kimlik 4 gecen 11 durdurulan 5 A:5/10 B:2/2 C:2/2 D:2/2 sema kapali yonlendirme adimi 5/5 son kod 308 sema acik yonlendirme adimi 0/5 son kod 200 yol gecirildi KTP%2F17 -> 200 Bolge Tarihi | KTP%252F17 -> 200 Bolge Tarihi Eki yol vekilde cozuldu KTP%2F17 -> 404 - | KTP%252F17 -> 200 Bolge Tarihi (yanlis kayit) sinir vekilde (4096) istemci 413 (vekil) vekil okudu 8192 uygulama okudu 0 uygulama kaydi 0 sinir uygulamada (4096) istemci 413 (uygulama) vekil okudu 8192 uygulama okudu 8192 uygulama kaydi 1
Sayılar belirlenimlidir: istek sayısı, sınır ve gövde boyu koşumdan bağımsız sabitlerdir, süre ölçülmez.
Tek Kova ve Dört Kova
İlk iki satır aynı on altı isteği aynı hız sınırından geçiriyor. Fark yalnız bir ayarda.
Aktarım kapalıyken uygulamanın denetim kaydında bir kimlik var: bütün istekler vekilin adresine yazılmış. Hız sınırı da tek kovaya düşüyor. Kovayı ilk gelen tüketiyor — üye A beş istek geçiriyor, kalan beşi durduruluyor, ardından gelen B, C ve D’nin altı isteğinin tamamı durduruluyor. Toplam: beş geçen, on bir durdurulan.
Aktarım açıkken kimlik sayısı dörde çıkıyor. A yine beş istek geçiriyor ve beşi durduruluyor; B, C ve D altı isteğin altısını da geçiriyor. Toplam: on bir geçen, beş durdurulan.
İki koşumda kötüye kullanan üyenin geçen istek sayısı aynı: beş. Değişen, iyi üyelerin sonucudur — aktarım kapalıyken altı iyi istek durduruldu, açıkken sıfır. Altı yanlış kalma, tek bir ayardan geliyor ve hiçbiri hata olarak görünmüyor; istemci geçerli bir 429 alıyor.
Denetim kaydı tarafındaki sonuç daha uzun ömürlüdür: kayıtta tek adres duruyor. Bir olayın hangi üyeden geldiği sorulduğunda kayıt yanıt veremez ve bunu da söylemez, çünkü kayıt eksik değil, yanlış doludur.
Beş Adımlık Döngü
Üçüncü ve dördüncü satır şema aktarımını ölçüyor. Uygulama isteğin şifreli gelmediğini görünce güvenli şemaya yönlendiriyor — bu, uygulama katmanında sık bir kuraldır.
Şema aktarımı kapalıyken istemci beş yönlendirme adımının beşini de tüketiyor ve elinde yalnız bir 308 kalıyor. Döngü kapalıdır: istemci güvenli şemaya gidiyor, vekil TLS’i yine sonlandırıyor, uygulamaya yine şifresiz bir bağlantı geliyor ve uygulama yine yönlendiriyor. Aktarım açıkken adım sayısı sıfır, kod 200.
Bu, yanlış ayarın sessiz olmayan ucudur: kullanıcı bir hata görür. Yine de kayıtlarda hata yoktur; uygulama her adımda doğru çalışmış, doğru bir yönlendirme üretmiştir.
Bir Kez Fazla Çözmek
Beşinci ve altıncı satır yol işlemeyi ölçüyor. Katalogda iki ayrı kayıt var: kimliği KTP/17 olan
kitap ve kimliği KTP%2F17 olan eki. İlki KTP%2F17 diye, ikincisi KTP%252F17 diye kodlanır.
Vekil yolu olduğu gibi geçirdiğinde ikisi de doğru kayda ulaşıyor. Vekil yolu bir kez çözdüğünde
iki isteğin ikisi de kayıyor. Birincisi /kitap/KTP/17 hâline geliyor; uygulama yol parçalarını
ayırdığında kitap kimliği KTP oluyor ve yanıt 404. İkincisi /kitap/KTP%2F17 hâline geliyor;
uygulama bunu kendi payına bir kez daha çözüyor, KTP/17 buluyor ve başka bir kitabı 200 ile
döndürüyor.
İki sonuç arasındaki fark önemlidir. 404 görünür bir arızadır. 200 ile dönen yanlış kayıt görünmez: istemci geçerli bir yanıt, geçerli bir kitap adı alır — yalnız istediği kitap değildir. Çözme işleminin kaç kez yapıldığı bir ayardır ve iki katman arasında bölünmüştür.
İki Sınır, Tek Etkin Değer
Son iki satır sekiz kilobaytlık bir gövdeyi iki farklı sınır yerleşiminden geçiriyor. Her iki yerleşimde de istemci 413 alıyor; etkin sınır her zaman ikisinin küçüğüdür. Değişen, faturanın ve kaydın yeridir.
Sınır vekildeyken vekil 8192 baytı okuyor, uygulamaya sıfır bayt geçiyor ve uygulamanın denetim kaydında sıfır istek görünüyor. Uygulamanın 16384’lük izni ölü bir ayardır: hiçbir zaman uygulanmaz. Reddedilen istek uygulama için hiç olmamıştır.
Sınır uygulamadayken aynı 8192 bayt iki kez okunuyor — bir kez vekilde, bir kez uygulamada — ve uygulamanın kaydında bir istek görünüyor. Ret aynı, maliyet iki katı, kayıt dolu.
İki satır aynı soruyu iki yönden yanıtlıyor: sınırın hangi katmanda durduğu isteğin reddedilip reddedilmeyeceğini değil, kimin çalıştığını ve kimin gördüğünü belirler.
Ayarın Yeri
Dört ayarın hepsi tek bir ölçüm tablosunda göründü, ama kodda dört ayrı yerde duruyorlar.
| Ayar | Yeri | Kaç yerde | Yanlış değerin sessiz sonucu |
|---|---|---|---|
| Başlık aktarımı | vekil | 1 (her vekil kopyası) | tek kimlik, altı yanlış kalma, yanlış dolu denetim kaydı |
| Şema aktarımı | vekil + uygulama kuralı | 2 | beş adımlık yönlendirme döngüsü |
| Yol işleme | vekil + uygulama çözmesi | 2 | 200 ile dönen yanlış kayıt |
| Gövde sınırı | vekil + uygulama | 2 | ölü ayar ya da iki kez okunan gövde |
Dört satırın üçünde ayar iki katmana bölünmüştür ve doğru davranış ikisinin uyumundan çıkar. Bir katman tek başına değiştirildiğinde uygulama çalışmaya devam eder; ölçü değişir. Vekil kopyası sayısı arttıkça birinci satırın “kaç yerde” değeri de artar: aktarımı açık unutulan tek bir kopya, oradan geçen bütün istekleri tek kimliğe yazar.
Özet
- TLS sonlandırma uygulamadan istemci adresini ve şemayı alır; ikisi de başlıkla aktarılır ve aktarım bir ayardır.
- Başlık aktarımı kapalıyken uygulamanın denetim kaydında bir kimlik göründü, açıkken dört; aynı on altı istekten beşi geçti ve on biri durduruldu, açıkken on biri geçti ve beşi durduruldu.
- Kötüye kullanan üyenin geçen istek sayısı iki koşumda da beşti; ayarın bedelini iyi üyeler ödedi ve altı istek yanlışlıkla durduruldu.
- Şema aktarımı kapalıyken istemci beş yönlendirme adımının beşini de tüketti ve 200 alamadı; açıkken sıfır adımda 200 aldı.
- Yol vekilde bir kez çözüldüğünde iki isteğin biri 404 aldı, biri başka bir kitabı 200 ile aldı; ikinci sonuç istemci tarafında hatasız görünür.
- Gövde sınırı vekildeyken uygulamaya sıfır bayt geçti ve denetim kaydında sıfır istek göründü; uygulamadayken aynı 8192 bayt iki kez okundu ve kayda bir istek düştü.
Sonraki Adım
Bu ders uygulamanın önündeki katmanı yapılandırdı. Ölçümlerin hepsi tek bir şeyi varsayıyordu: uygulama süreci ayaktaydı. Sekiz koşumun her birinde süreç elle başlatıldı, işi bitince elle durduruldu ve arada hiç çökmedi. Üretimde süreci başlatan, çöktüğünde yeniden başlatan ve ne kadar bellek kullanabileceğine karar veren başka bir bileşen vardır. Onun ayarları da sessizdir ve sonuçları vekilinkinden daha serttir: yanlış bir yeniden başlatma politikası çöken bir süreci saniyede onlarca kez ayağa kaldırmayı dener, yanlış bir bellek sınırı çalışan bir süreci ortasında keser. Sonraki ders süreç yöneticisini yazar ve üç yeniden başlatma politikasının düşen istek sayısını, bellek sınırının aşılmasında öldürme ile yavaşlatma arasındaki farkı ve zarif kapanmanın boşaltılan istek sayısına etkisini ölçer.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.