Ders 03 / 16
Kaynaklar Arası Erişim Denetimi
Kaynaklar arası paylaşımın sunucu tarafındaki yapılandırması: yedi kökenin dört ayar altında kaç tanesinin yanıtı kullanabildiği, joker ayarın açtığı köken sayısı, kimlik bilgisi taşıyan istekte joker ayarın sunucuda 200 döndürüp istemcide sessizce atılması, ön denetim isteğinin gidiş-dönüş maliyeti ve politikanın kaç uçta kayıtlı olduğu.
İçindekiler
Önceki iki ders isteğin gövdesine baktı. Bu ders isteğin nereden geldiğine bakıyor ve sunucunun bu konuda söyleyebileceği tek şeyi ölçüyor: yanıtın başlıkları.
Kaynaklar arası paylaşımın istemci tarafı — tarayıcının hangi isteği neden engellediği, politikanın sayfa üzerindeki etkisi — Frontend Kalitesi kursunda kuruldu ve burada tekrarlanmaz. Bu derste ölçülen şey sunucu tarafındaki yapılandırmadır: ayar hangi katmanda duruyor, joker bir değer kaç kökene kapı açıyor, kimlik bilgisi taşıyan bir istekte yanlış ayar ne yapıyor ve ön denetim isteği kaça mal oluyor.
Sunucunun yaptığı iş küçüktür: gelen kökene bakar, birkaç yanıt başlığı ekler ya da eklemez. Kararı istemci verir. Bu ayrım, dersin bütün ölçümlerinin dayandığı noktadır.
BG5 (varsayım): istemcinin kuralı bu derste kendi yazıldı. Sunucu ayarının doğruluğu, o kurala göre yanıtın kullanılıp kullanılmadığıyla ölçülüyor; ölçüm gerçek bir istemcinin değil, kuralın gerçekleştiriminin sonucudur.
Sunucunun Tek Sözü
// politika.mjs — sunucunun kaynaklar arasi paylasim konusundaki tek sozu: yanit basliklari export const AYAR = { kokenler: ["https://odunc.kutuphane", "https://panel.kutuphane"], // "*" joker demektir yontemler: ["GET", "POST"], basliklar: ["content-type", "x-odunc-izlem"], kimlikBilgisi: false, onDenetimOmru: 600, }; export function basliklar(istek, ayar) { const c = {}; if (!istek.koken) return c; // ayni kokenli istek; soz gerekmez const joker = ayar.kokenler === "*"; if (!joker && !ayar.kokenler.includes(istek.koken)) return c; // suskunluk = ret c["access-control-allow-origin"] = joker ? "*" : istek.koken; if (ayar.kimlikBilgisi) c["access-control-allow-credentials"] = "true"; if (!joker) c["vary"] = "origin"; // yanit kokene gore degisiyor if (istek.yontem === "OPTIONS") { c["access-control-allow-methods"] = ayar.yontemler.join(","); c["access-control-allow-headers"] = ayar.basliklar.join(","); c["access-control-max-age"] = String(ayar.onDenetimOmru); } return c; }
// istemci.mjs — uyumlu bir istemcinin yanit basliklarina bakarak verdigi karar // Kural burada kendi yazildi; sunucu ayarinin dogrulugu bu karara gore olculuyor. export function karar(istek, b) { if (!istek.koken) return { kullanildi: true, neden: "ayni koken" }; const izin = b["access-control-allow-origin"]; if (!izin) return { kullanildi: false, neden: "izin basligi yok" }; if (izin !== "*" && izin !== istek.koken) return { kullanildi: false, neden: "koken uyusmuyor" }; if (istek.kimlikBilgisi) { if (izin === "*") return { kullanildi: false, neden: "joker + kimlik bilgisi" }; if (b["access-control-allow-credentials"] !== "true") return { kullanildi: false, neden: "kimlik bilgisi izni yok" }; } return { kullanildi: true, neden: "" }; }
Politika katmanının reddi bir hata değil, suskunluktur: tanımadığı bir kökene hiçbir başlık eklemez. Sunucu yine 200 döner, gövde yine yazılır. Bu tasarım kararı dersin geri kalanındaki bütün sessiz sonuçların kaynağıdır.
Joker Ayarın Açtığı Köken
Yedi köken alınıyor: ödünç sisteminin kendi arayüzü, iç panel, aynı adın şema ve kapı farkları, bir alt ad, ilgisiz bir köken ve köken başlığı hiç taşımayan aynı kökenli istek.
// koken.mjs — yedi koken, dort ayar; sunucunun dondugu ile istemcinin kullandigi import { AYAR, basliklar } from "./politika.mjs"; import { karar } from "./istemci.mjs"; const KOKENLER = [ null, // ayni koken (baslik yok) "https://odunc.kutuphane", // kendi arayuzu "https://panel.kutuphane", // ic panel "http://odunc.kutuphane", // sema farkli "https://odunc.kutuphane:8443", // kapi farkli "https://alt.odunc.kutuphane", // alt ad "https://baska.site", // ilgisiz ]; const AYARLAR = [ ["liste, kimliksiz", { ...AYAR }], ["joker, kimliksiz", { ...AYAR, kokenler: "*" }], ["joker, kimlikli", { ...AYAR, kokenler: "*", kimlikBilgisi: true }], ["liste, kimlikli", { ...AYAR, kimlikBilgisi: true }], ]; const gec = (ayar, koken, kimlikBilgisi) => { const istek = { koken, yontem: "GET", kimlikBilgisi }; return karar(istek, basliklar(istek, ayar)).kullanildi; }; console.log(`${KOKENLER.length} koken (1 ayni koken + ${KOKENLER.length - 1} dis koken), ${AYARLAR.length} ayar`); console.log(`\n${"ayar".padEnd(19)}${"sunucu 2xx".padStart(11)}${"istemci kullandi: kimliksiz".padStart(28)}${"kimlikli".padStart(10)}`); for (const [ad, ayar] of AYARLAR) { const k0 = KOKENLER.filter((k) => gec(ayar, k, false)).length; const k1 = KOKENLER.filter((k) => gec(ayar, k, true)).length; console.log(ad.padEnd(19) + String(KOKENLER.length).padStart(11) + String(k0).padStart(28) + String(k1).padStart(10)); } const jokerAcik = KOKENLER.filter((k) => k && gec({ ...AYAR, kokenler: "*" }, k, false)); const listeAcik = KOKENLER.filter((k) => k && gec(AYAR, k, false)); console.log(`\nliste ayarinin actigi dis koken: ${listeAcik.length} (${listeAcik.join(", ")})`); console.log(`joker ayarinin bu kumede actigi dis koken: ${jokerAcik.length}; kume disinda sinir yok`); console.log(`\n'joker, kimlikli' ayarinda kimlik bilgisi tasiyan istekler:`); for (const k of KOKENLER.slice(1, 4)) { const istek = { koken: k, yontem: "GET", kimlikBilgisi: true }; const b = basliklar(istek, { ...AYAR, kokenler: "*", kimlikBilgisi: true }); const c = karar(istek, b); console.log(` ${k.padEnd(30)} sunucu 200 + ${Object.keys(b).length} baslik -> istemci: ${c.kullanildi ? "kullandi" : "atti (" + c.neden + ")"}`); }
7 koken (1 ayni koken + 6 dis koken), 4 ayar ayar sunucu 2xx istemci kullandi: kimliksiz kimlikli liste, kimliksiz 7 3 1 joker, kimliksiz 7 7 1 joker, kimlikli 7 7 1 liste, kimlikli 7 3 3 liste ayarinin actigi dis koken: 2 (https://odunc.kutuphane, https://panel.kutuphane) joker ayarinin bu kumede actigi dis koken: 6; kume disinda sinir yok 'joker, kimlikli' ayarinda kimlik bilgisi tasiyan istekler: https://odunc.kutuphane sunucu 200 + 2 baslik -> istemci: atti (joker + kimlik bilgisi) https://panel.kutuphane sunucu 200 + 2 baslik -> istemci: atti (joker + kimlik bilgisi) http://odunc.kutuphane sunucu 200 + 2 baslik -> istemci: atti (joker + kimlik bilgisi)
sunucu 2xx sütunu dört ayarda da yedidir. Sunucu hiçbir isteği reddetmiyor; ayar yalnız
istemcinin yanıtı kullanıp kullanmayacağını belirliyor. Liste ayarı iki dış kökene açıktır;
joker ayarı bu kümedeki altısına ve kümenin dışında sınır yoktur — şema farkı, kapı
farkı ve alt ad dahil, adı hiç yazılmamış her köken içeri girer. Joker ile listenin farkı
bu satırda 2’ye karşı 6’dır; gerçekte 2’ye karşı sınırsızdır.
Üçüncü satır bu dersin sessiz ayarıdır. kimlikBilgisi açılmış, sunucu iki başlık birden
gönderiyor, istek 200 dönüyor — ve istemci yanıtı atıyor. joker, kimlikli satırı
joker, kimliksiz satırıyla birebir aynı: ayarı açmak hiçbir şeyi değiştirmedi. Bir kayıt
yığınında bu iki ayar ayırt edilemez; iki durumda da 200 vardır, hata yoktur, gecikme
yoktur. Yalnız iç panel çalışmaz ve nedeni sunucu tarafında görünmez.
Doğru ayar son satırdır: köken listesi ve kimlik bilgisi izni birlikte. Üç kimlikli istek ancak orada kullanılıyor.
Ön Denetimin Maliyeti
Politika katmanı gerçek bir süreçte ölçülüyor. Özel bir başlık taşıyan istek, gönderilmeden önce bir ön denetim isteği gerektirir; ön denetimin sonucu bir ömür boyunca saklanabilir.
// sunucu.mjs — odunc uclarini dinleyen yerel surec; politika katmani uclarin onunde durur import http from "node:http"; import { basliklar } from "./politika.mjs"; export const UCLAR = ["/katalog", "/odunc", "/uye", "/rapor", "/arsiv"]; export function baslat(ayar, kapsanan = UCLAR) { const sayac = { istek: 0, onDenetim: 0, bayt: 0 }; const s = http.createServer((q, y) => { sayac.istek++; if (q.method === "OPTIONS") sayac.onDenetim++; const istek = { koken: q.headers.origin ?? null, yontem: q.method }; const b = kapsanan.includes(q.url) ? basliklar(istek, ayar) : {}; const govde = q.method === "OPTIONS" ? "" : JSON.stringify({ uc: q.url, durum: "ok" }); sayac.bayt += govde.length + Object.entries(b).reduce((t, [k, v]) => t + k.length + v.length + 4, 0); y.writeHead(q.method === "OPTIONS" ? 204 : 200, { "content-type": "application/json", ...b }); y.end(govde); }); return new Promise((c) => s.listen(0, "127.0.0.1", () => c({ port: s.address().port, sayac, kapat: () => s.close() }))); }
// olcum.mjs — gercek istekler: on denetim maliyeti ve katman kapsaminin olcusu import { baslat, UCLAR } from "./sunucu.mjs"; import { AYAR } from "./politika.mjs"; import { karar } from "./istemci.mjs"; const KOKEN = "https://panel.kutuphane"; const N = 6; // Uyumlu istemci: ozel baslikli istek once on denetim ister, sonuc omru boyunca onbelleklenir. async function gonder(port, uc, { ozel, onbellek, kimlikBilgisi = false }) { const kok = `http://127.0.0.1:${port}`; if (ozel && !(onbellek && onbellek.has(uc))) { const o = await fetch(kok + uc, { method: "OPTIONS", headers: { origin: KOKEN } }); if (onbellek) onbellek.set(uc, Number(o.headers.get("access-control-max-age") ?? 0)); } const basliklar = { origin: KOKEN, ...(ozel ? { "x-odunc-izlem": "1" } : {}) }; const y = await fetch(kok + uc, { headers: basliklar }); const b = Object.fromEntries(y.headers); return { durum: y.status, karar: karar({ koken: KOKEN, kimlikBilgisi }, b) }; } const { port, sayac, kapat } = await baslat(AYAR); const kosum = async (ad, secenek) => { const once = { ...sayac }; for (let i = 0; i < N; i++) await gonder(port, "/katalog", secenek); console.log(`${ad.padEnd(28)}${String(sayac.istek - once.istek).padStart(8)}` + `${String(sayac.onDenetim - once.onDenetim).padStart(12)}${String(sayac.bayt - once.bayt).padStart(9)}`); }; console.log(`${N} gercek istek, tek uc\n`); console.log(`${"istek bicimi".padEnd(28)}${"gidis-donus".padStart(8)}${"on denetim".padStart(12)}${"bayt".padStart(9)}`); await kosum("basit (on denetim gerekmez)", { ozel: false }); await kosum("ozel baslikli, onbelleksiz", { ozel: true }); await kosum("ozel baslikli, onbellekli", { ozel: true, onbellek: new Map() }); console.log(`on denetim omru: ${AYAR.onDenetimOmru} sn -> omur boyunca uc basina 1 on denetim`); kapat(); // Katman kapsami: politika bes ucun dordune kaydedilmis. const EKSIK = UCLAR.filter((u) => u !== "/arsiv"); const iki = await baslat({ ...AYAR, kimlikBilgisi: true }, EKSIK); console.log(`\npolitika kaydi ${EKSIK.length}/${UCLAR.length} ucta\n`); console.log(`${"uc".padEnd(10)}${"sunucu durumu".padStart(14)}${"istemci".padStart(9)} neden`); let atilan = 0; for (const uc of UCLAR) { const r = await gonder(iki.port, uc, { ozel: false, kimlikBilgisi: true }); if (!r.karar.kullanildi) atilan++; console.log(uc.padEnd(10) + String(r.durum).padStart(14) + (r.karar.kullanildi ? "kullandi" : "atti").padStart(9) + " " + (r.karar.neden || "-")); } console.log(`\n${UCLAR.length} ucun ${atilan} tanesinde yanit istemcide atildi; sunucunun dondugu hata sayisi: 0`); iki.kapat();
6 gercek istek, tek uc istek bicimi gidis-donus on denetim bayt basit (on denetim gerekmez) 6 0 588 ozel baslikli, onbelleksiz 12 6 1758 ozel baslikli, onbellekli 7 1 783 on denetim omru: 600 sn -> omur boyunca uc basina 1 on denetim politika kaydi 4/5 ucta uc sunucu durumu istemci neden /katalog 200 kullandi - /odunc 200 kullandi - /uye 200 kullandi - /rapor 200 kullandi - /arsiv 200 atti izin basligi yok 5 ucun 1 tanesinde yanit istemcide atildi; sunucunun dondugu hata sayisi: 0
Ön denetimin maliyeti koşumdan bağımsız bir sayıdır: önbelleksiz altı özel istek on iki gidiş-dönüş yapıyor, yani iki katı. Ömür ayarı devreye girdiğinde sayı yediye düşüyor — uç başına bir ön denetim. Politika baytı da üçe katlanıyor: 588’e karşı 1758.
Bu maliyet yalnız özel başlıklı isteklere aittir. İlk satır bunu gösteriyor: özel başlık kullanmayan altı istek hiç ön denetim üretmiyor. Ön denetimden kaçınmanın yolu ayarı gevşetmek değil, isteğin biçimini sadeleştirmektir.
Ayarın Yeri
Son tablo katman sorusudur. Politika beş ucun dördünde kayıtlı; /arsiv ucu unutulmuş.
O uç istekleri kabul ediyor, gövdeyi yazıyor, 200 dönüyor. Yanıt istemcide atılıyor ve
sunucu tarafında bunun hiçbir izi yok: hata sayısı sıfır, durum kodu diğer dört uçla aynı.
Ayarın kaç yerde tekrarlandığı doğrudan bu riski belirliyor. Politika her uç işleyicisine
ayrı kaydediliyorsa beş kayıt noktası vardır ve altıncı uç eklendiğinde altıncı kayıt elle
yazılmalıdır. Politika uçların önünde tek bir katman olarak duruyorsa kayıt noktası birdir
ve yeni uç kendiliğinden kapsanır — sunucu.mjs içindeki kapsanan listesi tam da bu iki
seçenek arasındaki farktır.
Özet
- Sunucunun kaynaklar arası paylaşımdaki tek sözü yanıt başlıklarıdır; reddi bir hata değil suskunluktur. Dört ayarın dördünde de sunucu yedi isteğin yedisine 2xx döndü.
- Köken listesi 2 dış kökene açıktır; joker ayar ölçülen kümedeki 6 kökene ve küme dışında sınırsız sayıda kökene açıktır — şema farkı, kapı farkı ve alt ad dahil.
- Joker ayara kimlik bilgisi izni eklemek hiçbir şeyi değiştirmedi:
joker, kimliklisatırıjoker, kimliksizile birebir aynı çıktı. Sunucu iki başlık gönderdi, 200 döndü, istemci yanıtı attı. Doğru ayar köken listesi ile kimlik bilgisi iznini birlikte kullanır. - Ön denetimin maliyeti koşumdan bağımsızdır: önbelleksiz 6 özel istek 12 gidiş-dönüş ve 1758 bayt, ömür ayarıyla 7 gidiş-dönüş ve 783 bayt, özel başlıksız 6 istek 0 ön denetim.
- Politika 5 ucun 4’üne kaydedilince atlanan uçtan gelen yanıt istemcide atıldı ve sunucuda 0 hata göründü. Uç başına kayıt 5 noktada tekrarlanır; uçların önündeki tek katman 1 noktada durur ve yeni ucu kendiliğinden kapsar.
Sonraki Adım
Kaynaklar arası paylaşım ayarı bir yanıt başlığı kümesinden ibaretti ve ölçüm gösterdi ki asıl soru başlığın hangi katmanda üretildiğidir. Aynı soru, yanıtın taşıdığı öteki başlıklar için de geçerlidir: bağlantının nasıl kurulacağını, içeriğin nasıl yorumlanacağını ve tarayıcının neyi yükleyebileceğini söyleyen başlıklar da aynı biçimde uç uç dağılabilir ya da tek yerden konabilir. Sonraki ders bu başlıkları sayar: kaç uçta tekrarlanıyorlar, tek katmana taşındığında kaç dosyaya dokunuluyor, eksik bir başlık hangi davranışı sessizce açık bırakıyor ve şifreli bağlantının sonlandığı nokta bu resmin neresinde duruyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.