---
title: 'HTTP Önbellekleme'
source: 'https://academia.sh/tr/kurslar/eszamansiz-isleme/http-onbellekleme'
course: 'Önbellekleme, Kuyruklar ve Eşzamansız İşleme'
language: tr
updated: '2026-08-17T18:06:47+00:00'
license: 'CC BY-SA 4.0'
---

# HTTP Önbellekleme

Yanıtın kendi önbellek kurallarını taşıması: ETag ve Last-Modified doğrulayıcılarının koşullu isteklerle 304 üretmesi, gövde aktarımının sıfıra inmesi, kaynak değiştiğinde doğrulayıcının değişmesi ve public, private, max-age, no-store ile yeniden doğrulayarak sunma yönergelerinin kaynak türüne göre seçilmesi.

Önceki derslerin bütün önbellekleri uygulamanın içindeydi: anahtar kodda üretiliyor, girdi
kodda konuyor, geçersizleştirme kodda tetikleniyordu. Yanıt istemciye gönderildiği anda bu
denetim biter. İlk derste "denetlenemez" diye anılan katmanlar — tarayıcı önbelleği,
aradaki paylaşılan önbellekler, kenar katmanı — kodun ne düşündüğünü bilmez; yalnız yanıtın
başlıklarında yazana bakar.

Bu yüzden HTTP'nin kendi önbellek sözlüğü vardır ve iki soruyu ayrı ayrı yanıtlar: bir yanıt
ne kadar süre **taze** sayılır, tazeliği bittiğinde saklanan kopyanın hâlâ geçerli olup
olmadığı kaynağa gövde indirmeden nasıl sorulur. Bu ders iki soruyu da gerçek istekler
üzerinde yanıtlar.

## Doğrulayıcı Üreten Bir Sunucu

Aşağıdaki sunucu kütüphane API'sinin dört uç noktasını sunar. Her yanıt gövdesinin özetinden
bir **doğrulayıcı** (validator) üretilir ve koşullu istekler bu değere göre yanıtlanır.
Yanıtlardaki tarih başlığı sabit tutulmuştur; böylece ölçümün çıktısı her koşuda aynıdır.

```js
// http-onbellek.mjs — kutuphane API'si: dogrulayicilar ve onbellek denetim yonergeleri
import { createServer } from "node:http";
import { createHash } from "node:crypto";

const kitaplar = new Map([[7, { kitapId: 7, baslik: "Kum: Borges Seçkisi", yazar: "Derleme", rafta: 3 }]]);
const stok = { subeId: 1, raftaki: 137 };
const gecmis = { uyeId: 4, toplam: 12 };
const DEGISIM = new Map([[7, new Date("2025-07-01T09:00:00Z")]]);   // son degisiklik anlari

const etiket = (govde) => `"${createHash("sha256").update(govde).digest("hex").slice(0, 16)}"`;

function yanitla(istek, yanit, { govde, denetim, sonDegisim = null, vary = null }) {
  const metin = JSON.stringify(govde);
  const etag = etiket(metin);
  const basliklar = { "cache-control": denetim, etag };
  if (sonDegisim) basliklar["last-modified"] = sonDegisim.toUTCString();
  if (vary) basliklar.vary = vary;

  const gelenEtag = istek.headers["if-none-match"];
  const gelenTarih = istek.headers["if-modified-since"];
  const etagUyuyor = gelenEtag !== undefined && gelenEtag.split(",").map((e) => e.trim()).includes(etag);
  const tarihUyuyor = !etagUyuyor && gelenTarih !== undefined && sonDegisim !== null &&
    Math.floor(sonDegisim.getTime() / 1000) <= Math.floor(Date.parse(gelenTarih) / 1000);

  if (etagUyuyor || tarihUyuyor) { yanit.writeHead(304, basliklar); return yanit.end(); }
  yanit.writeHead(200, { ...basliklar, "content-type": "application/json; charset=utf-8",
    "content-length": Buffer.byteLength(metin) });
  yanit.end(metin);
}

const sunucu = createServer((istek, yanit) => {
  yanit.sendDate = false;                                          // ciktinin belirlenimci kalmasi icin
  const yol = new URL(istek.url, "http://yerel").pathname;

  if (istek.method === "POST" && yol === "/kitap/7") {              // kitap guncellenir: dogrulayici degisir
    const k = kitaplar.get(7);
    k.rafta -= 1;
    DEGISIM.set(7, new Date("2025-07-02T14:30:00Z"));
    yanit.writeHead(204, { "cache-control": "no-store" });
    return yanit.end();
  }
  if (yol === "/kitap/7") {                                         // seyrek degisen genel kaynak
    return yanitla(istek, yanit, { govde: kitaplar.get(7), denetim: "public, max-age=60",
      sonDegisim: DEGISIM.get(7), vary: "Accept-Language" });
  }
  if (yol === "/sube/1/stok") {                                     // sik degisen genel kaynak
    return yanitla(istek, yanit, { govde: stok, denetim: "public, max-age=5, stale-while-revalidate=30" });
  }
  if (yol === "/uye/4/gecmis") {                                    // kisiye ozel kaynak
    return yanitla(istek, yanit, { govde: gecmis, denetim: "private, max-age=30" });
  }
  if (yol === "/odunc/onay") {                                      // hic saklanmamasi gereken yanit
    return yanitla(istek, yanit, { govde: { onay: "U-4711", kalan: 2 }, denetim: "no-store" });
  }
  yanit.writeHead(404, { "cache-control": "no-store" });
  yanit.end();
});

sunucu.listen(8547, "127.0.0.1", () => console.log("kutuphane API 127.0.0.1:8547"));
```

Sunucuyu başlatan ve ölçen blok ayrıdır. Doğrulayıcı ile son değişiklik tarihi yanıttan
okunur, sonraki isteklere koşul başlığı olarak konur.

```bash
# Yanitlarin onbellek basliklari ve kosullu isteklerin sonucu
node http-onbellek.mjs & sunucu=$!
sleep 0.5
K=http://127.0.0.1:8547

basliklar() { curl -sS -D - -o /dev/null "$@" | tr -d '\r' | grep -iE '^(HTTP/|etag|last-modified|cache-control|vary)'; }
olc() { curl -sS -o /dev/null -w "durum=%{http_code} indirilen=%{size_download} bayt\n" "$@"; }

echo "== 1. ilk istek: dogrulayicilar ve yonergeler"
basliklar "$K/kitap/7"

ETAG=$(curl -sS -D - -o /dev/null "$K/kitap/7" | tr -d '\r' | awk '/^[Ee]tag:/ {print $2}')
TARIH=$(curl -sS -D - -o /dev/null "$K/kitap/7" | tr -d '\r' | sed -n 's/^[Ll]ast-[Mm]odified: //p')
echo "== 2. ayni kaynak kosullu istenirse"
olc "$K/kitap/7"
olc -H "If-None-Match: $ETAG" "$K/kitap/7"
olc -H "If-Modified-Since: $TARIH" "$K/kitap/7"

echo "== 3. kaynak degisince eski dogrulayici tutmuyor"
curl -sS -o /dev/null -X POST "$K/kitap/7"
olc -H "If-None-Match: $ETAG" "$K/kitap/7"
basliklar "$K/kitap/7" | grep -iE '^(etag|last-modified)'

echo "== 4. kaynak turune gore yonergeler"
for yol in /kitap/7 /sube/1/stok /uye/4/gecmis /odunc/onay; do
  printf '%-16s %s\n' "$yol" "$(curl -sS -D - -o /dev/null "$K$yol" | tr -d '\r' | sed -n 's/^[Cc]ache-[Cc]ontrol: //p')"
done

kill $sunucu
```

```
kutuphane API 127.0.0.1:8547
== 1. ilk istek: dogrulayicilar ve yonergeler
HTTP/1.1 200 OK
cache-control: public, max-age=60
etag: "c5397b0c31b92e65"
last-modified: Tue, 01 Jul 2025 09:00:00 GMT
vary: Accept-Language
== 2. ayni kaynak kosullu istenirse
durum=200 indirilen=73 bayt
durum=304 indirilen=0 bayt
durum=304 indirilen=0 bayt
== 3. kaynak degisince eski dogrulayici tutmuyor
durum=200 indirilen=73 bayt
etag: "211739eb1d385801"
last-modified: Wed, 02 Jul 2025 14:30:00 GMT
== 4. kaynak turune gore yonergeler
/kitap/7         public, max-age=60
/sube/1/stok     public, max-age=5, stale-while-revalidate=30
/uye/4/gecmis    private, max-age=30
/odunc/onay      no-store
```

## Tazelik ve Doğrulama

Çıktının ikinci bölümü iki farklı işi ayırıyor. Koşulsuz istek 200 döndürdü ve 73 bayt
indirildi. Aynı kaynak `If-None-Match` başlığıyla istendiğinde yanıt 304 oldu ve indirilen
gövde sıfır bayta indi. Üçüncü satırda aynı sonuç `If-Modified-Since` ile alındı.

**304 Not Modified** yanıtının kazandırdığı şey gövdedir, gidiş-dönüş değil. İstek yine
sunucuya gitti, sunucu yine doğrulayıcıyı hesapladı. Kazanç, gövde büyüdükçe büyür: yetmiş
üç baytlık bir kitap kaydında ihmal edilebilir, elli kilobayttan bir liste yanıtında
belirleyicidir.

Tazelik ise doğrulamadan önce gelir. `max-age=60` yazan bir yanıt altmış saniye boyunca
**taze** sayılır ve o süre içinde önbellek sunucuya hiç sormaz. Süre dolduğunda kopya
atılmaz, **bayat** duruma geçer ve bir sonraki istekte doğrulanır. Doğrulama 304 dönerse
kopya yeniden taze sayılır. İki mekanizma birlikte çalışır: `max-age` gidiş-dönüşü, doğrulayıcı
gövdeyi ortadan kaldırır.

Üçüncü bölüm doğrulayıcının anlamını kanıtlıyor. Kitap POST isteğiyle güncellendikten sonra
elde tutulan eski doğrulayıcıyla yapılan koşullu istek 304 değil 200 döndü ve gövde yeniden
indirildi. Yeni yanıtın hem etiketi hem son değişiklik tarihi değişmiştir. Doğrulayıcı,
kaynağın içeriğine bağlı olduğu için bu davranış kendiliğinden doğrudur.

İki doğrulayıcı arasında bir ayrım vardır. Son değişiklik tarihinin çözünürlüğü bir
saniyedir: aynı saniye içinde iki kez değişen bir kayıt için eski kopya geçerli sanılabilir.
Gövde özetinden üretilen etiketin böyle bir sınırı yoktur. Buna karşılık etiketin
hesaplanması gövdenin üretilmesini gerektirir; tarih ise çoğu zaman kaydın kendi
sütunundan okunur ve sorgu çalıştırmadan yanıtlanabilir.

## Yönergelerin Anlamı

Son bölümdeki dört satır, dört farklı kaynak türü için verilmiş dört karardır.

`public, max-age=60` — kitap ayrıntısı her kullanıcıya aynı döner ve seyrek değişir.
`public`, yanıtın **paylaşılan** önbelleklerde saklanabileceğini söyler.

`public, max-age=5, stale-while-revalidate=30` — şube stok sayısı sık değişir. Beş saniye
taze sayılır; sonraki otuz saniye boyunca bayat kopya sunulmayı sürdürür ve önbellek arka
planda yenilemeyi yapar. Bu, önceki derste süreç içinde kurulan davranışın HTTP'deki
karşılığıdır; aradaki fark, kararı artık uygulamanın değil aradaki önbelleğin uygulamasıdır.

`private, max-age=30` — üyenin ödünç geçmişi kişiye özeldir. `private`, yanıtın yalnız son
kullanıcının kendi önbelleğinde saklanabileceğini, paylaşılan bir önbelleğe konamayacağını
söyler. Anahtar Tasarımı dersindeki kiracı sızıntısının HTTP karşılığı budur ve tek satırlık
bir başlıkla önlenir.

`no-store` — ödünç onayı hiçbir yerde saklanmamalıdır. Bu yönerge sıkça `no-cache` ile
karıştırılır: `no-cache` saklamayı yasaklamaz, yalnız kullanmadan önce doğrulamayı zorunlu
kılar. Saklanmaması gereken yanıt için doğru yönerge `no-store`'dur.

Paylaşılan önbellekler için ayrı bir süre verilebilir: `s-maxage`, yalnız paylaşılan
önbellekleri bağlar ve `max-age`'i geçersiz kılar. Kenar katmanında uzun, tarayıcıda kısa bir
süre istendiğinde kullanılan yol budur.

## Vary ve Paylaşılan Anahtar

Kitap yanıtındaki `Vary: Accept-Language` başlığı, paylaşılan önbelleğin anahtarını
değiştirir. Anahtar Tasarımı dersinde önbellek anahtarının yanıtı değiştiren her girdiyi
taşıması gerektiği ölçülmüştü; HTTP'de bu girdiler istek başlıklarıdır ve `Vary` onları adıyla
bildirir. Başlık verilmezse önbellek anahtar olarak yalnız adresi kullanır ve Türkçe isteyen
bir kullanıcıya İngilizce yanıt sunulabilir.

`Vary` başlığının tehlikeli iki değeri vardır. `Vary: Cookie` ya da `Vary: Authorization`
yazan bir yanıt teknik olarak doğrudur ama pratikte önbelleklenemez: her kullanıcının
çerezi farklı olduğu için her istek ayrı bir anahtara düşer ve isabet oranı sıfıra iner. Bu,
Anahtar Tasarımı dersinde ölçülen "fazla alan" satırının aynısıdır. Kişiye özel yanıtlarda
doğru çözüm `Vary` değil `private`'tır.

## Özet

- HTTP önbelleklemesi iki ayrı mekanizmadır: `max-age` yanıtın ne kadar süre taze sayılacağını,
  doğrulayıcı ise bayat kopyanın hâlâ geçerli olup olmadığını belirler.
- Koşullu istek 304 döndürdüğünde indirilen gövde 73 bayttan 0 bayta indi; gidiş-dönüş yine
  yapıldı. Kazanç gövdededir ve gövde büyüdükçe artar.
- Kaynak güncellendiğinde gövde özetinden üretilen etiket ve son değişiklik tarihi değişti;
  eski doğrulayıcıyla yapılan istek 304 yerine 200 aldı.
- Son değişiklik tarihinin çözünürlüğü bir saniyedir; gövde özetinden üretilen etiketin böyle
  bir sınırı yoktur ama üretilmesi gövdenin hesaplanmasını gerektirir.
- `public` paylaşılan önbelleğe izin verir, `private` yalnız son kullanıcının önbelleğine,
  `no-store` hiçbir yere. `no-cache` saklamayı değil, kullanmadan önce doğrulamamayı yasaklar.

## Sonraki Adım

Konu boyunca her ders bir sayı üretti: kaynağa giden sorgu sayısı, bayat okuma sayısı, isabet
ve ıska, indirilen bayt. Bu sayılar tek tek anlamlıydı ama hiçbiri "önbellek işe yarıyor mu"
sorusunu tek başına yanıtlamaz. Anahtar Tasarımı dersinde en yüksek isabet oranı yanlış
gerçekleştirime aitti; bir başka kurulumda yüksek isabet oranı, hiç okunmayan ucuz anahtarlardan
gelebilir. Sonraki ders bu sayıları bir ölçüt kümesine bağlar: isabet oranı nasıl hesaplanır,
neyi gizler, ıska cezası ile birleştiğinde ortalama yanıt süresine ne yapar ve önbellek
kapasitesi değiştikçe isabet oranı nasıl davranır.
