İçeriğe geç
academia.sh

Ders 14 / 19

Ortam Yapılandırması

Derleme zamanı değişkenlerinin çıktıya nasıl girdiği; ikamenin ürettiği sabitler, istemci paketine geçen her değerin herkese açık olması, geçebilen ile geçemeyen değer ayrımının denetlenebilir bir kurala dönüştürülmesi ve çalışma zamanı yapılandırmasının ödünleşimi.

İçindekiler

Bir önceki ders derlemenin iki kipini ayırdı, ama iki kipin de ortak bir varsayımı vardı: kaynak aynı. Kuzey Yamaç Ölçüm İstasyonu arayüzü yerelde bir deneme ucundan, yayında gerçek ölçüm hattından veri okur; günlük ayrıntısı yerelde açık, yayında kapalıdır; arşiv yazma işlemi yerelde geçici bir dizine, yayında kalıcı bir depoya gider. Bu farkların kaynağa yazılması aynı kaynaktan iki ayrı çıktı üretilmesi demektir ve o noktada tek bir kaynak ağacından söz edilemez.

Farkı taşıyan şey derleme zamanı değişkenleridir (build-time variables). Bu ders önce bu değişkenlerin çıktıya nasıl girdiğini gösterir, sonra tek kritik kuralı kurar: istemci paketine giren bir değer herkese açıktır.

İki Yapılandırma Anı

Sunucuda çalışan bir program yapılandırmasını çalışma zamanında okur. Süreç başlarken ortamdan bir adı sorar, değeri alır, kullanır. Değer değiştiğinde süreç yeniden başlatılır; program yeniden derlenmez. Ortam değişkenlerinin bir dosyadan — sürüm denetimine girmeyen bir .env dosyasından — yüklenmesi bu okumanın kaynağını değiştirir, zamanını değiştirmez.

Tarayıcıda çalışan kodun böyle bir kaynağı yoktur. Sayfa bir süreç ortamının içinde açılmaz; okuyabileceği tek şey kendisine verilmiş baytlardır. Yapılandırmanın istemci koduna girmesi için iki yol kalır ve ikisi ayrı anlarda çalışır.

Derleme zamanı ikamesi. Kaynaktaki bir yer tutucu, derleme sırasında değerin kendisiyle değiştirilir. Çıktıda değişken yoktur; sabit vardır. Değer değiştiğinde yeniden derleme gerekir.

Çalışma zamanı okuması. Değer çıktıya değil, belgeye ya da ayrı bir uca konur; istemci onu ilk isteğinde alır. Değer değiştiğinde yeniden derleme gerekmez, ama bir okuma daha gerekir.

Bu dersin ana konusu birincisidir, çünkü ikincisinin bir ön koşulu vardır: hangi değerin istemciye hiç verilmeyeceğine önce karar edilmiş olmalıdır.

İkamenin Yaptığı Şey

İkame bir metin işlemidir. Aşağıdaki dönüştürücü, kaynaktaki ORTAM.<AD> yer tutucularını ortam tablosundaki değerlerin kod olarak yazılmış hâliyle değiştirir ve tek bir paket üretir.

// ikame.mjs — derleme zamani ortam ikamesi ve ciktida deger aramasi.
import { mkdirSync, writeFileSync, readFileSync, rmSync } from "node:fs";

// Kaynak agac: uc modul, ucu de ORTAM.<AD> bicimindeki bir yer tutucu okuyor.
const KAYNAK = {
  "olcum-istemcisi.js":
    'const UC = ORTAM.KUZEY_OLCUM_UCU;\n' +
    'export const olcumleriGetir = (istasyon) =>\n' +
    '  fetch(UC + "/istasyon/" + istasyon).then((y) => y.json());\n',
  "gunluk.js":
    'const SIRA = { ayrinti: 0, bilgi: 1, uyari: 2, hata: 3 };\n' +
    'const DUZEY = ORTAM.KUZEY_GUNLUK_DUZEYI;\n' +
    'export const yaz = (duzey, ileti) =>\n' +
    '  SIRA[duzey] >= SIRA[DUZEY] ? console.log(duzey + ": " + ileti) : undefined;\n',
  "arsiv-yazici.js":
    'const ANAHTAR = ORTAM.KUZEY_YAZMA_ANAHTARI;\n' +
    'export const arsivle = (kayit) =>\n' +
    '  fetch("/arsiv", { method: "POST", headers: { "X-Anahtar": ANAHTAR },\n' +
    '                    body: JSON.stringify(kayit) });\n',
};

// .env dosyasinin icerigi: ad=deger satirlari, surume girmez.
const ENV = [
  "KUZEY_OLCUM_UCU=https://olcum.kuzey-yamac.example/v1",
  "KUZEY_GUNLUK_DUZEYI=uyari",
  "KUZEY_YAZMA_ANAHTARI=yaz-9f21c4a7e08b",
].join("\n") + "\n";

const cozumle = (metin) => Object.fromEntries(metin.split("\n")
  .filter((s) => s.includes("="))
  .map((s) => [s.slice(0, s.indexOf("=")).trim(), s.slice(s.indexOf("=") + 1).trim()]));

const ortam = cozumle(ENV);

// Ikame: ORTAM.<AD>, degerin kaynak koda yazilmis sabiti ile degistirilir.
const ikameEt = (kaynak) => kaynak.replace(/ORTAM\.([A-Z0-9_]+)/g,
  (tam, ad) => (ad in ortam ? JSON.stringify(ortam[ad]) : tam));

rmSync("cikti", { recursive: true, force: true });
mkdirSync("cikti", { recursive: true });
mkdirSync("kaynak", { recursive: true });
writeFileSync(".env.yayin", ENV);

let paket = "";
const degisen = [];
for (const [ad, govde] of Object.entries(KAYNAK)) {
  writeFileSync("kaynak/" + ad, govde);
  const cikti = ikameEt(govde);
  paket += "// " + ad + "\n" + cikti + "\n";
  govde.split("\n").forEach((satir, i) => {
    if (satir.includes("ORTAM.")) degisen.push([ad, cikti.split("\n")[i]]);
  });
}
writeFileSync("cikti/paket.js", paket);

console.log("-- ikame edilmis satirlar --");
for (const [ad, satir] of degisen) console.log("  " + ad.padEnd(20) + satir);

const govde = readFileSync("cikti/paket.js", "utf8");
console.log("-- cikti paketinde deger aramasi --");
for (const [ad, deger] of Object.entries(ortam))
  console.log("  " + ad.padEnd(22) + (govde.includes(deger) ? "GORUNUYOR" : "yok"));

Betiğin kendi araması dışında, üretilen dosyada bağımsız bir arama da yapılır.

#!/usr/bin/env bash
# Ikameyi calistirir, sonra uretilen paketi dogrudan tarar.
node ikame.mjs
echo "=== bagimsiz arama ==="
grep -c 'yaz-9f21c4a7e08b' cikti/paket.js
grep -n 'X-Anahtar' cikti/paket.js
-- ikame edilmis satirlar --
  olcum-istemcisi.js  const UC = "https://olcum.kuzey-yamac.example/v1";
  gunluk.js           const DUZEY = "uyari";
  arsiv-yazici.js     const ANAHTAR = "yaz-9f21c4a7e08b";
-- cikti paketinde deger aramasi --
  KUZEY_OLCUM_UCU       GORUNUYOR
  KUZEY_GUNLUK_DUZEYI   GORUNUYOR
  KUZEY_YAZMA_ANAHTARI  GORUNUYOR
=== bagimsiz arama ===
1
15:  fetch("/arsiv", { method: "POST", headers: { "X-Anahtar": ANAHTAR },

Çıktının söylediği şey teknik olarak sıradan, sonuçları bakımından değil: üç değerin üçü de paketin baytlarında düz metin olarak durur. Yazma anahtarı, kaynakta bir ad arkasındaydı; çıktıda bir dizgi sabitidir.

Bu sonuç bir kusur değil, ikamenin tanımıdır. Çıktıda değişkeni çözecek bir düzenek bulunmadığı için değerin oraya yazılmış olması gerekir. Aynı nedenle, ikame edilen değeri çıktıda gizleyecek bir derleme adımı da yoktur:

  • Küçültme değerleri değiştirmez. Ad kısaltma yerel adları değiştirir; dizgi sabitleri olduğu gibi kalır. Üçüncü dersteki ağaç sarsma da bir dizgiyi elemek için bir neden bulmaz.
  • Kodlama gizleme değildir. Değeri taban 64 ile ya da basit bir karıştırmayla yazmak, çözücüsü aynı pakette bulunduğu için değeri kaldırmaz; adım sayısını artırır.
  • Kaynak haritası ikinci bir kopyadır. Yayında herkese açık sunulan bir kaynak haritası, ikame edilmiş satırların özgün hâlini de taşır.

Buradan dersin kuralı çıkar: istemci paketine giren bir değer, sayfayı açan herkesin elindedir. İkameyle giren de, çalışma zamanında verilen de aynı durumdadır.

Gizli Değer Sınırı

Kural bir yasak listesi değil, bir sınıflandırmadır. Her yapılandırma değeri iki kümeden birine girer ve ayrım tek bir soruyla yapılır: bu değeri bilen biri, bilmeyen birinin yapamayacağı bir iş yapabilir mi?

Yanıt “hayır” ise değer istemciye geçebilir. Bu küme genel yapılandırmadır: açık uçların adresleri, günlük düzeyi, özellik anahtarlarının açık/kapalı durumu, sürüm etiketi, genel kiracı ya da site kimliği, yerel ayar listesi. Bunları bilmek yeni bir yetki vermez; zaten gözlemlenebilir olan şeyi söyler.

Yanıt “evet” ise değer istemciye geçemez. Bu küme gizli değerlerdir: sunucu tarafı yazma anahtarları, imzalama sırları, veri deposu bağlantı bilgileri, yönetim uçlarının kimlik bilgileri, üçüncü taraf hizmetlerin gizli anahtarları. Bunları bilen biri, sunucunun yetkisiyle iş yapabilir.

Sınırın iki sonucu vardır ve ikisi de sık ihmal edilir.

İhtiyaç bir istisna üretmez. İstemcinin bir gizli değere ihtiyaç duyduğu görülüyorsa, ihtiyaç duyulan şey değer değil, o değerle yapılan iştir. Doğru çözüm işi sunucuya taşımaktır: istemci kendi kökenindeki bir uca çağrı yapar, yetkiyi oturum taşır, gizli değer sunucuda kalır. Yukarıdaki arşiv yazıcısının doğru biçimi budur.

Sızmış bir değer geri alınamaz. Çıktı indirilmiş, ara önbelleklere ve tarayıcı önbelleklerine yerleşmiştir; beşinci derste eski adların bir süre daha yaşadığı da söylenmişti. Yayını geri almak değeri geri getirmez. Tek karşılık, değerin geçersiz kılınması ve yenisiyle değiştirilmesidir. Bu yüzden sınır, sızıntıdan sonra düzeltilecek bir şey değil, derlemeyi durduran bir denetimdir.

Aynı gerekçe derlemenin yapıldığı yere de uzanır. Gizli değerler bir sürekli tümleştirme hattında ortama verilir; hattın günlüğüne yazılan her satır, o hattın çıktısını görebilen herkese açıktır. İkameyi yapan aracın hangi adları çıktıya koyduğunu günlüğe basması, sızıntının en sessiz biçimidir.

Sınırı Denetlemek

Sınıflandırma bir belgede kaldığı sürece uygulanmaz. Denetlenebilir olması için üç şey gerekir: adların kapsamının tek bir yerde bildirilmesi, bildirimin adlandırmaya yansıması ve çıktının bildirime karşı sınanması.

Aşağıdaki denetleyici bunu beş kuralla yapar. K1 bildirilmemiş adı, K2 istemci kaynağında okunan sunucu kapsamlı adı, K3 açık önek taşımayan istemci adını, K4 çıktı baytlarında bulunan bir gizli değeri, K5 ortamda karşılığı olmadığı için çıktıda kalmış yer tutucuyu bildirir.

// denetci.mjs — istemciye acik degisken ile sunucuda kalan degiskeni ayiran denetleyici.
// Kullanim: node denetci.mjs ilk | node denetci.mjs duzeltilmis
const ONEK = "KUZEY_ACIK_";

// Bildirim: her adin kapsami tek bir yerde yazilir. Denetim bu tabloya bakar.
const BILDIRIM = {
  KUZEY_ACIK_OLCUM_UCU: "istemci",
  KUZEY_ACIK_GUNLUK_DUZEYI: "istemci",
  KUZEY_ACIK_SURUM_ETIKETI: "istemci",
  KUZEY_OLCUM_UCU: "istemci",
  KUZEY_GUNLUK_DUZEYI: "istemci",
  KUZEY_YAZMA_ANAHTARI: "sunucu",
  KUZEY_IMZALAMA_SIRRI: "sunucu",
};

const YAZ = 'const ANAHTAR = ORTAM.KUZEY_YAZMA_ANAHTARI;\n' +
  'export const arsivle = (kayit) => istek("/arsiv", ANAHTAR, kayit);\n';

const SURUM = {
  ilk: {
    istemci: {
      "olcum-istemcisi.js": 'const UC = ORTAM.KUZEY_OLCUM_UCU;\n',
      "gunluk.js": 'const DUZEY = ORTAM.KUZEY_GUNLUK_DUZEYI;\n',
      "dipnot.js": 'export const dipnot = () => "surum " + ORTAM.KUZEY_ACIK_SURUM_ETIKETI;\n',
      "arsiv-yazici.js": YAZ,
    },
    sunucu: {},
    env: {
      KUZEY_OLCUM_UCU: "https://olcum.kuzey-yamac.example/v1",
      KUZEY_GUNLUK_DUZEYI: "uyari",
      KUZEY_YAZMA_ANAHTARI: "yaz-9f21c4a7e08b",
      KUZEY_IMZALAMA_SIRRI: "imza-4d70b1cc52ae",
    },
  },
  duzeltilmis: {
    istemci: {
      "olcum-istemcisi.js": 'const UC = ORTAM.KUZEY_ACIK_OLCUM_UCU;\n',
      "gunluk.js": 'const DUZEY = ORTAM.KUZEY_ACIK_GUNLUK_DUZEYI;\n',
      "dipnot.js": 'export const dipnot = () => "surum " + ORTAM.KUZEY_ACIK_SURUM_ETIKETI;\n',
      // Arsiv yazimi ayni kokendeki uca tasindi; anahtar istemci kaynaginda gecmiyor.
      "arsiv-gonderici.js": 'export const arsivle = (kayit) => istek("/uc/arsiv", kayit);\n',
    },
    sunucu: { "arsiv-yazici.js": YAZ },
    env: {
      KUZEY_ACIK_OLCUM_UCU: "https://olcum.kuzey-yamac.example/v1",
      KUZEY_ACIK_GUNLUK_DUZEYI: "uyari",
      KUZEY_ACIK_SURUM_ETIKETI: "2026.03",
      KUZEY_YAZMA_ANAHTARI: "yaz-9f21c4a7e08b",
      KUZEY_IMZALAMA_SIRRI: "imza-4d70b1cc52ae",
    },
  },
};

const okunanlar = (kaynaklar) => [...new Set(Object.values(kaynaklar)
  .flatMap((g) => [...g.matchAll(/ORTAM\.([A-Z0-9_]+)/g)].map(([, ad]) => ad)))];

const paketle = (kaynaklar, env) => Object.values(kaynaklar).join("")
  .replace(/ORTAM\.([A-Z0-9_]+)/g, (tam, ad) => (ad in env ? JSON.stringify(env[ad]) : tam));

function denetle(surum) {
  const { istemci, sunucu, env } = SURUM[surum];
  const paket = paketle(istemci, env);
  const bulgu = [];
  const ekle = (kural, ad, ileti) => bulgu.push([kural, ad, ileti]);

  for (const ad of okunanlar(istemci)) {
    if (!(ad in BILDIRIM)) ekle("K1", ad, "bildirimde yok");
    else if (BILDIRIM[ad] === "sunucu") ekle("K2", ad, "sunucu kapsamli ad istemcide okunuyor");
    else if (!ad.startsWith(ONEK)) ekle("K3", ad, "istemci kapsamli ad " + ONEK + " onekini tasimiyor");
    if (!(ad in env)) ekle("K5", ad, "ortamda tanimli degil, yer tutucu ciktida kaldi");
  }
  for (const [ad, kapsam] of Object.entries(BILDIRIM))
    if (kapsam === "sunucu" && ad in env && paket.includes(env[ad]))
      ekle("K4", ad, "degeri istemci paketinin baytlarinda bulundu");

  console.log("surum: " + surum);
  console.log("  istemci paketi: " + Object.keys(istemci).length + " modul, okunan ad: " +
    okunanlar(istemci).join(", "));
  console.log("  sunucu paketi : " + Object.keys(sunucu).length + " modul, okunan ad: " +
    (okunanlar(sunucu).join(", ") || "-"));
  for (const [kural, ad, ileti] of bulgu)
    console.log("  " + kural + "  " + ad.padEnd(26) + ileti);
  console.log("  ihlal: " + bulgu.length);
  return bulgu.length;
}

process.exitCode = denetle(process.argv[2]) > 0 ? 1 : 0;
#!/usr/bin/env bash
# Iki surumu de denetler ve cikis kodlarini yazar.
node denetci.mjs ilk;          echo "cikis kodu: $?"
node denetci.mjs duzeltilmis;  echo "cikis kodu: $?"
surum: ilk
  istemci paketi: 4 modul, okunan ad: KUZEY_OLCUM_UCU, KUZEY_GUNLUK_DUZEYI, KUZEY_ACIK_SURUM_ETIKETI, KUZEY_YAZMA_ANAHTARI
  sunucu paketi : 0 modul, okunan ad: -
  K3  KUZEY_OLCUM_UCU           istemci kapsamli ad KUZEY_ACIK_ onekini tasimiyor
  K3  KUZEY_GUNLUK_DUZEYI       istemci kapsamli ad KUZEY_ACIK_ onekini tasimiyor
  K5  KUZEY_ACIK_SURUM_ETIKETI  ortamda tanimli degil, yer tutucu ciktida kaldi
  K2  KUZEY_YAZMA_ANAHTARI      sunucu kapsamli ad istemcide okunuyor
  K4  KUZEY_YAZMA_ANAHTARI      degeri istemci paketinin baytlarinda bulundu
  ihlal: 5
cikis kodu: 1
surum: duzeltilmis
  istemci paketi: 4 modul, okunan ad: KUZEY_ACIK_OLCUM_UCU, KUZEY_ACIK_GUNLUK_DUZEYI, KUZEY_ACIK_SURUM_ETIKETI
  sunucu paketi : 1 modul, okunan ad: KUZEY_YAZMA_ANAHTARI
  ihlal: 0
cikis kodu: 0

İlk sürümün beş bulgusu üç ayrı kusur sınıfını gösterir.

Adlandırma kusuru. İki K3 bulgusu bir sızıntı değildir; sızıntıyı görünmez kılan koşuldur. Açık önek, bir adı okuyan geliştiriciye o değerin çıktıya gireceğini adın kendisi üzerinden söyler. Önek yoksa K2 ile K3 arasındaki fark bir dikkat meselesine iner.

Eksik yapılandırma. K5, ortamda karşılığı olmayan bir adın ikame edilmeden çıktıda kaldığını bildirir. Bu durumda çıktıda tanımsız bir başvuru kalır ve hata çalışma zamanında, o satır yürütüldüğünde ortaya çıkar. Derleme zamanında yakalanabilecek bir kusurun kullanıcı tarafında ortaya çıkması, denetimin var oluş nedenidir.

Sınır ihlali. K2 ile K4 aynı sorunun iki farklı yerden görülmüş hâlidir: biri kaynağa, öteki çıktının baytlarına bakar. İkisi birden gerekir. Kaynak denetimi, ada dokunmadan değeri elle yazan bir kodu yakalayamaz; bayt denetimi ise adın nereden geldiğini söyleyemez.

Düzeltilmiş sürümde arşiv yazıcısı istemci kümesinden çıkarılıp sunucu kümesine alınmıştır. İstemcide kalan modül aynı kökendeki bir uca çağrı yapar; anahtar hiçbir aşamada istemci kaynağına girmez.

Yeniden Derlemeyi Gerektirmeyen Yapılandırma

Derleme zamanı ikamesinin bir bedeli vardır ve bu bedel dağıtım konusunun sorusudur: ikame edilen her değer, çıktıyı o ortama bağlar. Beş ortam varsa beş ayrı derleme çıktısı olur ve önizlemede sınanan baytlar, yayına giden baytlar değildir.

Çalışma zamanı yapılandırması bu bağı çözer. Ortama göre değişen değerler pakete girmez; belgeye küçük bir bölüm olarak ya da ayrı bir uca konur, istemci onları okuyup kullanır. Aynı derleme çıktısı her ortamda çalışır.

Ödünleşim üç ölçüde okunur. Derleme sayısı ortam sayısı kadardan bire iner. Buna karşılık ilk görünüme kadar okunması gereken kaynak sayısı artar; değer ayrı bir uçtan geliyorsa bu bir ağ turu daha demektir. Değer belgeye gömülürse tur eklenmez, ama belgenin önbelleklenebilirliği düşer: beşinci dersin politikasında belge zaten her kullanımda yeniden doğrulanıyordu, dolayısıyla bu iki karar birbiriyle uyumludur.

Seçim ölçütü şudur: bir değer ortamlar arasında değişiyor ve aynı çıktının birden çok ortamda çalışması isteniyorsa çalışma zamanında verilir; değer bütün ortamlarda aynıysa ya da değerinin derleme kararlarını etkilemesi gerekiyorsa — örneğin ölü kod elemesinin bir özelliği tümüyle çıkarabilmesi için — derleme zamanında ikame edilir.

İki yolun değiştirmediği tek şey sınırdır. Çalışma zamanında verilen bir değer de istemciye ulaştığı anda herkese açıktır; onu belgeye yazmak, pakete yazmaktan farklı bir güvenlik durumu üretmez.

Özet

  • Tarayıcıda çalışan kodun süreç ortamı yoktur; yapılandırma ona ya derleme zamanında ikame edilerek ya da çalışma zamanında okutularak ulaşır.
  • İkame, yer tutucuyu değerin sabiti ile değiştirir; örnekte üç değerin üçü de çıktı paketinin baytlarında düz metin olarak bulunur.
  • Küçültme, kodlama ve ad kısaltma değerleri gizlemez; kaynak haritası ikinci bir kopya taşır. İstemci paketine giren değer, sayfayı açan herkesin elindedir.
  • Ayrım tek soruyla yapılır: değeri bilen biri, bilmeyen birinin yapamayacağı bir iş yapabiliyorsa değer gizlidir ve istemciye geçemez; iş sunucuya taşınır.
  • Sınır denetlenebilir olmalıdır: kapsam bildirimi, açık önek ve hem kaynağa hem çıktı baytlarına bakan bir denetim, ihlalde derlemeyi durdurur.
  • Çalışma zamanı yapılandırması derleme sayısını bire indirir ama bir okuma ekler; sızmış bir gizli değer ise ancak geçersiz kılınarak karşılanır.

Sonraki Adım

Derleme çıktısı artık belirlenmiş durumda: parçaları, varlıkları, karmalı adları ve ortamına bağlı sabitleriyle bir dizin. Bu dizin bir makinenin diskinde durduğu sürece kimsenin işine yaramaz. Sıradaki soru, o baytların kullanıcının tarayıcısına hangi yolla ulaştığıdır. En yalın yol, çıktıyı olduğu gibi kopyalayıp kullanıcılara yakın duran düğümlerden sunmaktır: hiçbir sunucu süreci çalışmaz, her istek bir dosyaya karşılık gelir. Sonraki ders bu yolu kurar, kenar önbelleğinde isabet ile ıskayı ölçer ve beşinci dersteki ada göre önbellek politikasının isabet oranına dönüşümünü hesaplar.

İ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