İçeriğe geç
academia.sh

Ders 01 / 19

İstemcide Oluşturma

İşaretlemenin tarayıcıda üretilmesi; boş kabuğun ne taşıdığı, ilk içeriğe kadar uzanan seri istek zinciri ve bu modelin ilk boyamada ödediği bedelle sonraki gezinmelerde kazandığı hız.

İçindekiler

Bir önceki kurs uygulamanın iç mimarisini kurdu: adresler görünümlere eşlendi, durum tek bir yerden yönetildi, veri katmanı isteği ve yeniden denemeyi üstlendi, formlar doğrulama kurallarını paylaştı. Bütün bunlar uygulamanın ne yaptığını tanımlar. Açıkta kalan soru şudur: bu uygulamanın kullanıcıya gönderilen işaretlemesi nerede ve ne zaman üretilir?

Kuzey Yamaç Ölçüm İstasyonu sitesi bu soruyu somutlaştırır. Sitede bir istasyon tanıtım sayfası, dakikada bir değişen bir ölçüm listesi, geçmiş günlerin binlerce arşiv sayfası ve istasyon sorumlusuna özel bir panel vardır. Bu sayfaların hepsi aynı yerde üretilmek zorunda değildir. Bu kurs, her biri için ayrı bir karar verir. İlk model, işaretlemeyi tarayıcıya ürettirendir.

Oluşturma Kararı Nedir, Ne Değildir

Oluşturma (rendering), bu kursta veri ile şablonun birleşip HTML işaretlemesine dönüşmesi anlamına gelir. Karar, bu birleşmenin hangi makinede ve hangi anda olduğuna ilişkindir: kullanıcının tarayıcısında mı, isteğe yanıt veren sunucuda mı, yoksa istek gelmeden çok önce derleme sırasında mı.

Bu, Tarayıcı ve Web Platformu kursunda geçen çizim hattı ile karıştırılmamalıdır. Çizim hattı, tarayıcının elindeki işaretlemeyi yerleşim, boyama ve bileşim aşamalarından geçirip piksele çevirmesidir; işaretleme hangi makinede üretilmiş olursa olsun bu hat tarayıcıda çalışır. Oluşturma kararı hattın girdisinin nereden geldiğini belirler, hattın kendisini değiştirmez.

Ayrımı korumak için kurs boyunca iki sözcük ayrı tutulur: işaretlemenin üretilmesi oluşturma, tarayıcının piksele çevirmesi çizim.

Sunucunun Gönderdiği Şey

İstemcide oluşturmada sunucu, veri içermeyen bir belge ve bir betik gönderir. Belgenin görevi betiği yüklemek ve betiğin yazacağı boş bir kap sağlamaktır.

// istemci-sunucu.mjs — istasyon sayfasini istemcide olusturan sunucu
import { createServer } from "node:http";

const OLCUMLER = [
  { zaman: "06:00", sicaklik: -4.2, nem: 72 },
  { zaman: "07:00", sicaklik: -3.8, nem: 70 },
  { zaman: "08:00", sicaklik: -2.1, nem: 66 },
];

const KABUK = `<!doctype html>
<html lang="tr"><head><meta charset="utf-8"><title>Kuzey Yamac Olcum Istasyonu</title>
<script src="/uygulama.js" defer></script></head>
<body><div id="istasyon"></div></body></html>
`;

const BETIK = `const kok = document.getElementById("istasyon");
const yanit = await fetch("/api/olcumler");
const olcumler = await yanit.json();
kok.innerHTML = "<h1>Kuzey Yamac Olcum Istasyonu</h1><ul>" +
  olcumler.map((o) => "<li>" + o.zaman + " " + o.sicaklik + " C, %" + o.nem + "</li>").join("") +
  "</ul>";
`;

const bekle = (ms) => new Promise((c) => setTimeout(c, ms));

createServer(async (istek, yanit) => {
  yanit.sendDate = false; // ciktiyi belirlenimci kilmak icin Date basligi yazilmaz
  if (istek.url === "/") {
    yanit.setHeader("Content-Type", "text/html; charset=utf-8");
    yanit.setHeader("Content-Length", Buffer.byteLength(KABUK));
    yanit.writeHead(200).end(KABUK);
  } else if (istek.url === "/uygulama.js") {
    yanit.setHeader("Content-Type", "text/javascript; charset=utf-8");
    yanit.setHeader("Content-Length", Buffer.byteLength(BETIK));
    yanit.writeHead(200).end(BETIK);
  } else if (istek.url === "/api/olcumler") {
    await bekle(40); // veri kaynagindan okuma isi
    const govde = JSON.stringify(OLCUMLER);
    yanit.setHeader("Content-Type", "application/json");
    yanit.setHeader("Content-Length", Buffer.byteLength(govde));
    yanit.writeHead(200).end(govde);
  } else {
    yanit.writeHead(404).end();
  }
}).listen(8171, "127.0.0.1", () => console.log("dinleniyor: 127.0.0.1:8171"));

Aşağıdaki betik bu dosyayla aynı dizinde çalıştırılır. Bağlantı noktası olarak seçilen 8171 keyfîdir ve boş olmalıdır; kullanımdaysa iki dosyada da değiştirilir.

#!/usr/bin/env bash
# istemci-sunucu.mjs'yi baslatir, uc istegi sirayla olcer, sonra durdurur.
node istemci-sunucu.mjs > /dev/null &
sunucu=$!
sleep 1

BICIM='  ilk bayt %{time_starttransfer} s   govde %{size_download} B\n'

echo "--- ilk belgenin govdesi ---"
curl -sS http://127.0.0.1:8171/

echo "--- belgede olcum degeri geciyor mu? ---"
curl -sS http://127.0.0.1:8171/ | grep -c -- "-4.2"

echo "--- zincirin uc adimi (sirayla) ---"
echo "1) /"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8171/
echo "2) /uygulama.js"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8171/uygulama.js
echo "3) /api/olcumler"
curl -sS -o /dev/null -w "$BICIM" http://127.0.0.1:8171/api/olcumler

kill "$sunucu"
--- ilk belgenin govdesi ---
<!doctype html>
<html lang="tr"><head><meta charset="utf-8"><title>Kuzey Yamac Olcum Istasyonu</title>
<script src="/uygulama.js" defer></script></head>
<body><div id="istasyon"></div></body></html>
--- belgede olcum degeri geciyor mu? ---
0
--- zincirin uc adimi (sirayla) ---
1) /
  ilk bayt 0.000732 s   govde 199 B
2) /uygulama.js
  ilk bayt 0.000669 s   govde 300 B
3) /api/olcumler
  ilk bayt 0.042841 s   govde 130 B

Süre alanları çalıştırıldığı makineye ve o andaki yüke bağlıdır; her çalıştırmada farklı çıkar. Anlamlı olan sayıların kendisi değil, aralarındaki orandır: ilk iki istek yerel bağlantıda mikrosaniyeler sürerken üçüncüsü, yapay olarak eklenmiş 40 milisaniyelik veri işi yüzünden yaklaşık altmış kat uzundur. Gerçek bir dağıtımda bu üç isteğin her birine bir de ağ turu eklenir.

İkinci çıktı satırı modelin tanımlayıcı özelliğidir: ilk belgede hiçbir ölçüm değeri yoktur. Belgeyi okuyan bir istemci — arama motoru gezgini, bağlantı önizlemesi üreten bir hizmet, betik çalıştırmayan bir okuyucu — boş bir kap görür.

Seri Zincirin Aritmetiği

Üç isteğin her biri bir öncekinin sonucuna bağlıdır. Belge gelmeden betiğin adresi bilinmez; betik yürütülmeden veri isteği doğmaz. Bu bağımlılık, sürelerin toplanmasına yol açar. Aşağıdaki hesap, ölçüm değil, açıkça yazılmış varsayımların aritmetiğidir.

// zincir.mjs — istemcide olusturmanin ilk icerige kadar olan seri zinciri
// Girdiler varsayimdir; cikti bu varsayimlarin aritmetigidir, olcum degildir.
const GIDIS_DONUS = 60;      // ms, tek tur
const SUNUCU_BELGE = 5;      // ms, kabugu uretme isi
const SUNUCU_VERI = 40;      // ms, veri ucunun isi
const BANT = 1.5 * 1024;     // KB/s
const BETIK_KB = 180;
const YURUTME_MS_KB = 0.35;  // KB basina ayristirma + yurutme

const aktarim = (kb) => (kb / BANT) * 1000;

const adimlar = [
  ["belge istegi (tur)", GIDIS_DONUS],
  ["kabugu uretme", SUNUCU_BELGE],
  ["kabuk aktarimi (0.2 KB)", aktarim(0.2)],
  ["betik istegi (tur)", GIDIS_DONUS],
  [`betik aktarimi (${BETIK_KB} KB)`, aktarim(BETIK_KB)],
  ["betik ayristirma + yurutme", BETIK_KB * YURUTME_MS_KB],
  ["veri istegi (tur)", GIDIS_DONUS],
  ["veri ucunun isi", SUNUCU_VERI],
  ["veri aktarimi (0.1 KB)", aktarim(0.1)],
  ["agaci kurma + boyama", 8],
];

let toplam = 0;
console.log("adim".padEnd(30) + "sure".padStart(8) + "birikimli".padStart(12));
for (const [ad, sure] of adimlar) {
  toplam += sure;
  console.log(ad.padEnd(30) + sure.toFixed(1).padStart(8) + toplam.toFixed(1).padStart(12));
}
console.log("-".repeat(50));
console.log("ilk icerik boyamasi".padEnd(30) + toplam.toFixed(1).padStart(20));

// Kabukta gorunen sey: yalnizca ilk iki adimin sonunda bos bir belge.
const bosBelge = GIDIS_DONUS + SUNUCU_BELGE + aktarim(0.2);
console.log("bos kabuk ekranda".padEnd(30) + bosBelge.toFixed(1).padStart(20));
console.log("bos ekranda gecen oran".padEnd(30) +
  ((bosBelge / toplam) * 100).toFixed(0).padStart(19) + "%");
adim                              sure   birikimli
belge istegi (tur)                60.0        60.0
kabugu uretme                      5.0        65.0
kabuk aktarimi (0.2 KB)            0.1        65.1
betik istegi (tur)                60.0       125.1
betik aktarimi (180 KB)          117.2       242.3
betik ayristirma + yurutme        63.0       305.3
veri istegi (tur)                 60.0       365.3
veri ucunun isi                   40.0       405.3
veri aktarimi (0.1 KB)             0.1       405.4
agaci kurma + boyama               8.0       413.4
--------------------------------------------------
ilk icerik boyamasi                          413.4
bos kabuk ekranda                             65.1
bos ekranda gecen oran                         16%

Tabloda üç kalem baskındır: üç ağ turu, betiğin aktarımı ve betiğin yürütülmesi. Üçü de kullanıcının gördüğü ilk içeriğin önünde durur. Kabuk ekrana geldiğinde toplam sürenin yalnızca altıda biri geçmiştir; kalan beşte beşinde ekranda içerik yoktur.

Bu tablo aynı zamanda hangi iyileştirmenin ne kadar getirdiğini gösterir. Betiği yarıya indirmek yaklaşık 90 milisaniye kazandırır. Veri isteğini belge isteğiyle birleştirmek bir turu ve veri ucunun işini zincirin dışına çıkarır. Ağ turlarının süresi ise kod değişikliğiyle azalmaz; mesafeye bağlıdır ve altıncı ders bu kalemi ayrıca ele alır.

Modelin Kazandırdıkları

İlk yükleme pahalıdır, ama bedelin karşılığı vardır ve bu karşılık göz ardı edilirse model haksız yere reddedilir.

Betik bir kez yürüdükten sonra sonraki gezinmeler ağdan bağımsızlaşır. Uygulama Mimarisi kursunda kurulan istemci yönlendiricisi adres değiştiğinde yeni bir belge istemez; eldeki durumdan yeni görünümü üretir. Ölçüm listesinden arşiv sayfasına geçiş, belge indirmeden gerçekleşir.

Sunucu tarafı durumsuz ve ucuzdur. Sunucunun gönderdiği belge her kullanıcı için aynıdır; kişiye göre değişen hiçbir şey üretmez. Böyle bir belge statik dosya olarak sunulabilir, uzun ömürle önbelleklenebilir ve bir içerik dağıtım ağının kopyalarından verilebilir.

İstemci–sunucu sınırı belirgindir. Sunucu yalnızca veri uçları sunar; işaretlemeyi üretme sorumluluğu tümüyle istemcidedir. Aynı veri uçları bir masaüstü ya da mobil kabuk tarafından da kullanılabilir; bu sınır, arayüzün çoğaltılmasını kolaylaştırır.

Uzun süre açık kalan, sık etkileşim alan ve her etkileşimde sunucuya dönmesi gerekmeyen arayüzler bu modelin doğal alanıdır. İstasyon sorumlusunun paneli — ölçüm eşiklerini düzenlediği, grafikleri yakınlaştırdığı, kayıtları süzdüğü ekran — buna örnektir.

Bedelin Ağırlaştığı Yerler

Aynı ödünleşim, üç durumda modeli yanlış seçim hâline getirir.

İlk ziyaretin sık olduğu sayfalarda bedel her seferinde ödenir. Bir arama sonucundan gelip tek sayfa okuyup ayrılan ziyaretçi, sonraki gezinmelerin hızından hiç yararlanmaz; yalnızca ilk yüklemenin gecikmesini görür.

İşaretlemeyi okuyan istemcilerde içerik yoktur. Çıktının ikinci satırı bunu göstermişti: belgede hiçbir ölçüm değeri geçmez. İçeriğin makine tarafından okunmasına bağlı sayfalarda bu tek başına belirleyicidir.

Yavaş aygıtlarda betiğin yürütülmesi aktarımından pahalı hâle gelir. Tabloda 63 milisaniye görünen ayrıştırma ve yürütme kalemi, işlemci gücü birkaç kat düşük bir aygıtta aynı oranda büyür; üstelik bant genişliği arttıkça bu kalem toplamın içinde daha büyük bir pay tutar. Betiğin boyutu yalnızca indirme süresini değil, cihazın harcadığı işi de belirler.

Dördüncü bir kırılganlık, betiğin hiç yürümemesidir. Aktarım kesilirse, bir hata betiği durdurursa ya da bir uzantı araya girerse kullanıcının gördüğü şey boş bir belgedir. Sunucu tarafında üretilmiş bir işaretlemede aynı arıza yalnızca etkileşimi kaybettirir, içeriği değil.

Özet

  • Oluşturma kararı, veri ile şablonun hangi makinede ve hangi anda işaretlemeye dönüştüğünü belirler; tarayıcının çizim hattı bu karardan bağımsız olarak her durumda istemcide çalışır.
  • İstemcide oluşturmada ilk belge veri taşımaz; ölçüm değerleri işaretlemede geçmez ve belgeyi betik çalıştırmadan okuyan istemciler boş bir kap görür.
  • İlk içeriğe kadar olan yol seridir: belge, betik ve veri istekleri birbirini bekler; toplam süreye üç ağ turu, betiğin aktarımı ve betiğin yürütülmesi eklenir.
  • Model, ilk yüklemeden sonra gezinmeleri ağdan bağımsızlaştırır ve sunucuyu durumsuz bırakır; uzun oturumlu, etkileşim yoğun ekranlar için uygundur.
  • Bedel, ilk ziyaretin sık olduğu, işaretlemenin makine tarafından okunduğu ve aygıt gücünün düşük olduğu durumlarda ağırlaşır.

Sonraki Adım

Bu dersteki zincirin üç ağ turundan ikisi, veri ile işaretlemenin ayrı ayrı taşınmasından doğuyordu. İkisi tek yanıtta birleştirilirse zincir kısalır: sunucu, veriyi okuyup şablonu kendisi işleyerek içeriği hazır gönderebilir. Bu, ilk içeriğin görünme anını öne çeker ama sunucunun her istek için iş yapmasını gerektirir ve önbelleklenmesi kolay bir yanıtı kişiye göre değişen bir yanıta dönüştürür. Sonraki ders bu takası ölçer: aynı istasyon sayfası sunucuda üretildiğinde ilk bayt süresi, gövde boyutu ve içerik nasıl değişir.

İ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