Ders 17 / 24
Servis Çalışanları
Sayfadan bağımsız yaşayan ve isteklere araya girerek yanıt verebilen katman; kapsam kuralı, kurulum–bekleme–etkinleşme döngüsü, istek karşılama sözleşmesi ve yanlış dağıtımın geri alınma yolu.
İçindekiler
Buraya kadar kurulan her şey sayfa yüklendikten sonra geçerlidir. Hepsinin öncesinde tek bir varsayım durur: bileşenin kodu, şablonu ve verisi ağdan gelmiştir. Bağlantı zayıfladığında bu varsayım düşer ve sayfa, ne kadar iyi kapsüllenmiş olursa olsun, bir hata ekranına iner. Kuzey Yamaç Ölçüm İstasyonu sayfasının kullanıldığı yer buna elverişli değildir: ölçüm alan kişi çoğunlukla kapsama alanının kıyısındadır.
Bu ders, sayfanın isteklerini ağa ulaşmadan önce görebilen ve sayfadan bağımsız yaşayan bir katmanı kurar: servis çalışanı (service worker).
Sayfadan Bağımsız Bir Bağlam
Servis çalışanı, Web İşçileri dersinde tanıtılan çalışan iş parçacığının bir türüdür ve onun bütün kısıtlarını taşır: belge ağacına erişimi yoktur, sayfanın değişkenlerini göremez, sayfayla yalnızca ileti geçirerek konuşur. Üzerine kendi kısıtlarını ekler.
Bir sayfaya bağlı değildir. Kaydeden sayfa kapansa da kayıt yerinde kalır; aynı kökenin başka bir sayfası açıldığında aynı çalışan devreye girer. Bu, onu bir sayfa bileşeni değil, kökene ait bir hizmet yapar.
Sürekli çalışmaz. Tarayıcı çalışanı bir olay geldiğinde uyandırır, iş bitince sonlandırır. Aradaki süre garanti edilmez. Buradan bağlayıcı bir kural çıkar: çalışanın genel kapsamındaki değişkenler kalıcı durum tutamaz. İki olay arasında kaybolabilirler. Kalıcı olması gereken her şey depolama katmanına yazılır.
Yalnızca güvenli bağlamda çalışır. Kayıt, HTTPS üzerinden sunulan bir köken (origin) gerektirir; geliştirme kolaylığı için geri döngü adresi ayrık tutulmuştur. Gerekçe doğrudandır: araya giren bir katman, ağı dinleyen birinin eline geçtiğinde sayfanın tamamını kalıcı olarak değiştirebilir. HTTPS’in Rolü dersindeki bütünlük güvencesi bu yetkinin önkoşuludur.
Kapsam ve Denetim
Bir kayıt, kökenin tamamını değil bir yol önekini yönetir. Bu öneke kapsam (scope) denir ve iki ayrı kural onu belirler.
Birincisi bildirimin geçerliliğidir: bir çalışan, betiğinin bulunduğu dizinden daha geniş
bir kapsam bildiremez. /varlik/js/ altındaki bir betik kökün tamamını yönetemez. Kısıt
aşılabilir, ama yalnızca sunucunun bu izni bir başlıkla vermesiyle — yani dosyayı yükleyen
kişinin değil, sunucuyu yapılandıran kişinin kararıyla.
İkincisi eşlemedir: bir sayfayı, kapsamı o sayfanın yolunun öneki olan kayıtlardan en uzunu denetler. IP Adresi Kavramı dersindeki en uzun önek eşlemesiyle aynı mantıktır.
// kapsam.mjs — kayit kapsaminin en uzun onek kuraliyla eslenmesi // Kapsam, betigin bulundugu dizinden genis olamaz; genisletmek icin sunucunun // izin basligi gerekir. Asagidaki iki fonksiyon bu kurallari ayri ayri sinar. const dizin = (yol) => yol.slice(0, yol.lastIndexOf("/") + 1); function kapsamGecerliMi(betikYolu, istenenKapsam, izinVerilenUst = null) { const enGenis = izinVerilenUst ?? dizin(betikYolu); return istenenKapsam.startsWith(enGenis); } // Bir sayfayi hangi kayit denetler: kapsami sayfanin yolunun oneki olan // kayitlardan EN UZUN olani. function denetleyen(kayitlar, sayfaYolu) { const uyanlar = kayitlar.filter((k) => sayfaYolu.startsWith(k.kapsam)); if (uyanlar.length === 0) return null; return uyanlar.reduce((a, b) => (b.kapsam.length > a.kapsam.length ? b : a)); } const denemeler = [ { betik: "/sw.js", kapsam: "/" }, { betik: "/istasyon/sw.js", kapsam: "/istasyon/" }, { betik: "/istasyon/sw.js", kapsam: "/" }, { betik: "/istasyon/sw.js", kapsam: "/", izin: "/" }, { betik: "/varlik/js/sw.js", kapsam: "/istasyon/" }, ]; console.log("kapsam bildirimi gecerli mi?"); for (const d of denemeler) { const sonuc = kapsamGecerliMi(d.betik, d.kapsam, d.izin); console.log( ` betik ${d.betik.padEnd(18)} kapsam ${d.kapsam.padEnd(12)}` + `${d.izin ? "izin " + d.izin : "izin yok"} → ${sonuc ? "gecerli" : "reddedilir"}`); } const kayitlar = [ { ad: "kok", kapsam: "/" }, { ad: "istasyon", kapsam: "/istasyon/" }, { ad: "kuzey-yamac", kapsam: "/istasyon/kuzey-yamac/" }, ]; console.log("\nsayfayi hangi kayit denetler?"); for (const sayfa of [ "/", "/hakkinda", "/istasyon/", "/istasyon/gunluk", "/istasyon/kuzey-yamac/olcumler", ]) console.log(` ${sayfa.padEnd(32)} → ${denetleyen(kayitlar, sayfa)?.ad ?? "(denetimsiz)"}`);
kapsam bildirimi gecerli mi? betik /sw.js kapsam / izin yok → gecerli betik /istasyon/sw.js kapsam /istasyon/ izin yok → gecerli betik /istasyon/sw.js kapsam / izin yok → reddedilir betik /istasyon/sw.js kapsam / izin / → gecerli betik /varlik/js/sw.js kapsam /istasyon/ izin yok → reddedilir sayfayi hangi kayit denetler? / → kok /hakkinda → kok /istasyon/ → istasyon /istasyon/gunluk → istasyon /istasyon/kuzey-yamac/olcumler → kuzey-yamac
Üçüncü ve dördüncü satırdaki fark, betik yerleşiminin bir dağıtım kararı olduğunu gösterir. Bir servis çalışanı betiğini varlık dizinine koymak, paketleyicinin çıktısını tek yere toplamak için doğal görünür ve kapsamı o dizine hapseder. Betiğin kök dizinde durması teknik bir zorunluluk değil, kapsam bildiriminin bedava geldiği yerdir.
Kapsam eşlemesinin bir sonucu da denetimsiz kalabilmektir: kapsam dışındaki bir sayfa hiçbir çalışan tarafından görülmez ve ağa doğrudan gider. Kapsam dışı sayfaların çevrimdışı davranışı yoktur.
Yaşam Döngüsü ve Bekleme Durumu
Bir servis çalışanı kaydedildiğinde doğrudan işe başlamaz. Aradaki durumlar, aynı sayfada iki farklı sürümün karışmasını önlemek için vardır.
Kurulum yeni sürümün ilk fırsatıdır: ihtiyaç duyacağı kaynakları önceden toplar. Kurulum başarısız olursa sürüm hiç etkinleşmez ve eski sürüm yerinde kalır — bu, yarım kalmış bir dağıtımın sayfayı bozmasını önleyen ilk emniyet kilididir.
Bekleme kurulumu biten sürümün, eski sürümün denetlediği sayfalar kapanana kadar beklemesidir. Gerekçesi, bir sayfanın ömrü boyunca tek bir sürümle konuşmasıdır: sayfa eski sürümün önbelleğine göre yüklenmişken isteklerinin yeni sürüm tarafından karşılanması, sürüm karışması üretir.
Etkinleşme eski sürümün bıraktığı artıkları temizlemek için doğru yerdir; o noktada eski sürüm hiçbir sayfayı denetlemiyordur.
// calisan-yasam-dongusu.mjs — kurulum, bekleme ve etkinlesme durumlarinin modeli const izleme = []; function kayitDefteri() { return { etkin: null, bekleyen: null, kurulan: null, denetlenen: new Set() }; } // Yeni surum kaydedilir: kurulur, sonra ya bekler ya dogrudan etkinlesir. function kaydet(kayit, surum, { beklemeyiAtla = false } = {}) { kayit.kurulan = surum; izleme.push(`${surum}: kuruluyor`); kayit.kurulan = null; const denetlenenVar = kayit.denetlenen.size > 0; if (kayit.etkin && denetlenenVar && !beklemeyiAtla) { kayit.bekleyen = surum; izleme.push(`${surum}: kuruldu → bekliyor (${kayit.etkin} hala ${kayit.denetlenen.size} sayfayi denetliyor)`); } else { etkinlestir(kayit, surum); } } function etkinlestir(kayit, surum) { if (kayit.etkin) izleme.push(`${kayit.etkin}: artik gecersiz`); kayit.etkin = surum; kayit.bekleyen = null; izleme.push(`${surum}: etkinlesiyor → etkin`); } // Denetim yalnizca gezinme aninda kurulur; sahiplenme cagrisi bunu beklemez. function sayfaAc(kayit, sayfa) { kayit.denetlenen.add(sayfa); izleme.push(`${sayfa} acildi → denetleyen: ${kayit.etkin ?? "(yok)"}`); } function sayfaKapat(kayit, sayfa) { kayit.denetlenen.delete(sayfa); izleme.push(`${sayfa} kapandi → denetlenen sayfa: ${kayit.denetlenen.size}`); if (kayit.bekleyen && kayit.denetlenen.size === 0) etkinlestir(kayit, kayit.bekleyen); } console.log("A. Bekleme durumu: eski surum sayfalari birakana kadar bekler"); const a = kayitDefteri(); kaydet(a, "s1"); sayfaAc(a, "olcumler"); kaydet(a, "s2"); sayfaAc(a, "gunluk"); sayfaKapat(a, "olcumler"); sayfaKapat(a, "gunluk"); console.log(izleme.map((s) => " " + s).join("\n")); izleme.length = 0; console.log("\nB. Beklemeyi atlama: yeni surum acik sayfalari devralir"); const b = kayitDefteri(); kaydet(b, "s1"); sayfaAc(b, "olcumler"); kaydet(b, "s2", { beklemeyiAtla: true }); console.log(izleme.map((s) => " " + s).join("\n")); console.log(` → 'olcumler' sayfasi s1 ile yuklendi, isteklerini artik ${b.etkin} karsiliyor`);
A. Bekleme durumu: eski surum sayfalari birakana kadar bekler s1: kuruluyor s1: etkinlesiyor → etkin olcumler acildi → denetleyen: s1 s2: kuruluyor s2: kuruldu → bekliyor (s1 hala 1 sayfayi denetliyor) gunluk acildi → denetleyen: s1 olcumler kapandi → denetlenen sayfa: 1 gunluk kapandi → denetlenen sayfa: 0 s1: artik gecersiz s2: etkinlesiyor → etkin B. Beklemeyi atlama: yeni surum acik sayfalari devralir s1: kuruluyor s1: etkinlesiyor → etkin olcumler acildi → denetleyen: s1 s2: kuruluyor s1: artik gecersiz s2: etkinlesiyor → etkin → 'olcumler' sayfasi s1 ile yuklendi, isteklerini artik s2 karsiliyor
Birinci bölümün altıncı satırı sık gözden kaçan davranıştır: s2 beklerken açılan yeni
sayfa da s1 tarafından denetlenir. Bekleme durumunu bitiren şey sekmenin yenilenmesi
değil, kökene ait bütün sayfaların kapanmasıdır. Sekmeyi yenilemek eski sayfayı
kapatmadan yenisini açtığı için beklemeyi çoğu zaman bitirmez.
İkinci bölüm beklemeyi atlamanın ne yaptığını ve neyi riske attığını gösterir. Yeni sürüm hemen etkinleşir ve açık sayfaları devralır. Kazanç dağıtımın anında yayılmasıdır; bedel, eski sürümle yüklenmiş bir sayfanın isteklerinin yeni sürüm tarafından karşılanmasıdır. Sayfanın kodu ile çalışanın beklentisi ayrıştığında bu kırılma üretir. Beklemeyi atlamak, sürümler arası uyumun sağlandığı bilinen dağıtımlarda güvenlidir; her dağıtımda uygulanan varsayılan bir alışkanlık değildir.
Kaydın güncellenmesi ayrı bir denetimdir: tarayıcı çalışan betiğini belirli aralıklarla yeniden ister ve gelen içerik öncekinden bayt düzeyinde farklıysa yeni sürüm sayar. Bu yüzden çalışan betiğinin kendisi uzun ömürlü olarak önbelleklenmemelidir; aksi hâlde güncelleme denetimi eski kopyayı okur ve dağıtım hiç görülmez.
İstekleri Araya Girerek Karşılamak
Etkin bir çalışan, denetlediği sayfaların ürettiği her ağ isteği için bir olay alır: sayfa gezinmeleri, betikler, biçem dosyaları, görseller, veri istekleri. Olay nesnesi isteği taşır; çalışan iki şeyden birini yapar.
Yanıt vermezse istek olağan yoluna devam eder ve ağa gider. Bu, hiçbir şey yapmamanın güvenli varsayılan olduğu anlamına gelir.
Yanıt verirse ağ hiç kullanılmayabilir. Yanıt bir söz olarak bildirilir; söz çözülene kadar istek bekler. Sözleşmenin katı bir kısıtı vardır: yanıt verme kararı olay geri çağrısı çalışırken eşzamanlı olarak bildirilmelidir. Karar bir söz beklendikten sonra verilirse geç kalınmış olur; tarayıcı bu arada isteği ağa yollamıştır. Yanıtı üretmek eşzamansız olabilir, yanıt vereceğini bildirmek olamaz.
İki ayrıntı sık atlanır. Birincisi, yanıt gövdesi bir kez okunur; hem önbelleğe yazıp hem sayfaya verilmesi gerekiyorsa kopyalanmalıdır. İkincisi, başka bir kökenden gelen ve başlıkları paylaşıma kapalı olan yanıtlar saydam değildir: durum kodu okunamaz, içeriği incelenemez, boyutu belirsizdir. Böyle bir yanıtın başarılı olup olmadığı anlaşılamadığı için önbelleğe yazılması, hata sayfasının kalıcılaşmasına yol açabilir.
Çalışanın göremediği istekler de vardır: kapsam dışındaki sayfaların istekleri, başka kökenlerin sayfalarından doğan istekler ve çalışanın kendi betiğinin güncelleme isteği.
Dağıtımın Geri Alınamazlığı
Servis çalışanı, sunucuya değil kullanıcının aygıtına dağıtılan bir vekildir. Bozuk bir sürüm sunucudan silinmekle ortadan kalkmaz; kullanıcının aygıtında etkin kalır ve sayfayı kendi bildiği hâline sabitler. Sunucudaki düzeltme, çalışan güncelleme denetimini yapıp yeni betiği kabul edene kadar kullanıcıya ulaşmaz.
Bunun karşılığı iki alışkanlıktır. Birincisi, çalışan betiğinin kısa ömürlü önbelleklenmesi — güncelleme denetiminin gerçekten ağa çıkması. İkincisi, kaydı silen ve önbellekleri boşaltan asgari bir sürümün hazır tutulması: ne yaptığı belirsiz bir çalışanı devreden çıkarmanın tek yolu, onun yerine kendini kaldıran bir çalışan dağıtmaktır.
Özet
- Servis çalışanı sayfaya değil kökene bağlıdır, olaylar arasında sonlandırılabilir ve genel kapsamında kalıcı durum tutamaz.
- Kapsam iki kuralla belirlenir: betiğin dizininden geniş olamaz, ve bir sayfayı öneki eşleşen kayıtlardan en uzunu denetler.
- Kurulum başarısız olursa sürüm etkinleşmez; bekleme durumu, bir sayfanın ömrü boyunca tek sürümle konuşmasını sağlar ve kökenin bütün sayfaları kapanana kadar sürer.
- Beklemeyi atlamak dağıtımı anında yayar, ama eski sayfayı yeni sürümle eşleştirir; sürüm uyumu bilinmeden yapılmaz.
- İstek karşılama kararı eşzamanlı bildirilmelidir; yanıt gövdesi bir kez okunur ve saydam olmayan yanıtların başarısı denetlenemez.
- Çalışan kullanıcının aygıtına dağıtılan bir vekildir; bozuk sürümü geri almanın yolu, kendini kaldıran bir sürüm dağıtmaktır.
Sonraki Adım
Bu ders araya girme yetkisini kurdu ama onunla ne yapılacağını açık bıraktı. İsteği karşılamanın tek bir doğru biçimi yoktur: istasyonun logosu ile ölçüm listesi aynı kuralla sunulamaz. Biri değişmez ve ağdan sorulması gereksizdir; diğeri dakikada bir değişir ve eski kopyası yanıltıcıdır. Aradaki seçim, hız ile tazelik arasında bilinçli bir ödünleşimdir ve her kaynak türü için ayrı verilir. Sonraki ders bu kararı, önbellek anahtarının nasıl üretildiği ve bir kopyanın ne zaman taze sayıldığı sorularıyla birlikte ele alır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.