İçeriğe geç
academia.sh

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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat