---
title: 'İstek Yaşam Döngüsü'
source: 'https://academia.sh/tr/kurslar/sunucu-temelleri/istek-yasam-dongusu'
course: 'Sunucu Tarafı Temelleri'
language: tr
updated: '2026-08-17T18:06:52+00:00'
license: 'CC BY-SA 4.0'
---

# İstek Yaşam Döngüsü

Bir isteğin bağlantı kabulünden son baytın yazılmasına uzanan yolu yedi aşamaya bölünür; her aşamaya zaman damgası konularak sıra ve süreler ölçülür, bağlantı ile isteğin ayrı şeyler olduğu ve durum satırının ne zaman geri alınamaz hâle geldiği gösterilir.

Önceki ders sürecin dışını ölçtü: kaç kopya çalışır, istek hangi kopyaya düşer, kopyalar
neyi paylaşmaz. Sürecin içinde ise tek bir isteğin geçtiği bir yol vardır ve o yol şimdiye
kadar tek bir kutu gibi ele alındı. Bu ders kutuyu açar.

İstek işleyicisi, dışarıdan bakıldığında "isteği al, yanıtı ver" biçiminde tek adımlı
görünür. Gerçekte arada, her biri kendi zamanında olan ve her biri sonrakini kısıtlayan
aşamalar vardır. Aşamaları görmenin en doğrudan yolu, her birine bir zaman damgası koyup
sırayı yazdırmaktır. Kütüphane ödünç servisinin iki ucu — kitap ödünç verme ve katalog
listeleme — bu ölçüm için kullanılacak.

## Yedi Aşama

Bir isteğin sunucu tarafındaki yolu şu adımlardan geçer:

1. **Bağlantı kabulü.** İşletim sistemi bir TCP bağlantısını dinleyen sokete koyar, uygulama
   onu kabul eder. Bu noktada henüz hiçbir HTTP verisi okunmamıştır.
2. **Başlık satırlarının çözülmesi.** İstek satırı ve başlıklar okunup ayrıştırılır. Yöntem,
   yol ve başlıklar artık bilinir; gövde bilinmez.
3. **Gövdenin okunması.** Gövde parça parça gelir; son parça geldiğinde istek tamdır.
4. **Yönlendirme.** Yöntem ve yol bir işleyiciye eşlenir. Eşleşme yoksa isteğin karşılığı
   `404` olur.
5. **İşleyicinin çalışması.** İş kuralı uygulanır, veri okunur veya yazılır, yanıt gövdesi
   üretilir.
6. **Yanıt başlığının yazılması.** Durum kodu ve başlıklar sokete yazılır.
7. **Yanıtın tamamlanması.** Gövdenin son baytı yazılır ve bağlantı ya kapanır ya da sonraki
   istek için açık kalır.

Bu sıra bir kural değil, bir gözlemdir; aşağıdaki sunucu her aşamayı gerçekleştiği anda
kaydeder.

## Aşamaları Ölçmek

```js
// yasam-dongusu.mjs — bir istegin gectigi asamalari zaman damgasiyla kaydeder ve yazdirir
import { createServer } from "node:http";
import { readFile } from "node:fs/promises";

const KATALOG = new Map([["978-0262033848", { ad: "Algoritmalara Giris", adet: 2 }]]);
const t0 = () => process.hrtime.bigint();
const ms = (a, b) => Number(b - a) / 1e6;                    // nanosaniye farki -> milisaniye

let baglantiNo = 0;

const sunucu = createServer();

sunucu.on("connection", (soket) => {                        // 1. TCP baglantisi kabul edildi
  soket.kabulAni = t0();
  soket.no = ++baglantiNo;
  soket.istekSayisi = 0;
});

sunucu.on("request", (istek, yanit) => {
  const soket = istek.socket;
  soket.istekSayisi++;
  const asamalar = [["baglanti kabulu", soket.kabulAni]];
  const kaydet = (ad) => asamalar.push([ad, t0()]);

  kaydet("baslik satirlari cozuldu");                       // 2. istek olayi = basliklar hazir

  const parcalar = [];
  istek.on("data", (parca) => parcalar.push(parca));
  istek.on("end", async () => {
    kaydet("govde okundu");                                 // 3. govdenin son baytini aldik

    const yol = new URL(istek.url, "http://yerel").pathname;
    const isleyici = yol === "/odunc" && istek.method === "POST" ? "oduncVer"
      : yol === "/kitaplar" ? "katalogListele" : null;
    kaydet(`yonlendirme -> ${isleyici ?? "yok"}`);           // 4. yol bir isleyiciye eslendi

    let durum = 404, govde = { hata: "yol_yok" };
    if (isleyici === "oduncVer") {
      const istem = JSON.parse(Buffer.concat(parcalar).toString() || "{}");
      const kitap = KATALOG.get(istem.isbn);
      await readFile(new URL(import.meta.url));              // is: gercek bir girdi-cikti
      durum = kitap && kitap.adet > 0 ? 201 : 409;
      govde = durum === 201 ? { isbn: istem.isbn, uye: istem.uye, kalan: --kitap.adet }
        : { hata: "raf_bos" };
    } else if (isleyici === "katalogListele") {
      durum = 200;
      govde = { kitaplar: [...KATALOG].map(([isbn, k]) => ({ isbn, ...k })) };
    }
    kaydet("isleyici bitti");                                // 5. yanit govdesi hazir

    const metin = JSON.stringify(govde);
    yanit.writeHead(durum, { "content-type": "application/json; charset=utf-8",
      "content-length": Buffer.byteLength(metin) });
    kaydet("baslik yazildi");                                // 6. durum satiri artik kesin

    yanit.end(metin);
    yanit.on("finish", () => {                               // 7. son bayt cekirdege verildi
      kaydet("yanit tamamlandi");
      const baslangic = asamalar[0][1];
      console.log(`\n#${soket.no}.${soket.istekSayisi}  ${istek.method} ${istek.url} -> ${durum}`);
      for (const [ad, an] of asamalar) {
        console.log(`  ${String(ms(baslangic, an).toFixed(3)).padStart(9)} ms  ${ad}`);
      }
    });
  });
});

sunucu.listen(8432, "127.0.0.1", () => console.log("yasam dongusu 127.0.0.1:8432"));
```

Ölçüm üç istek yapar: aynı bağlantı üzerinde bir ödünç verme ve bir katalog listeleme, sonra
ayrı bir bağlantıda bilinmeyen bir yol. `--next` seçeneği aynı `curl` çağrısı içinde ikinci
bir istek tanımlar ve bağlantı yeniden kullanılır.

```bash
#!/usr/bin/env bash
# yasam-dongusu.mjs'yi baslatir; tek baglantida iki istek ve ayri baglantida bir istek yapar.
node yasam-dongusu.mjs & sunucu=$!
sleep 0.6

echo "### tek baglanti uzerinde iki istek (curl baglantiyi yeniden kullanir)"
curl -sS -o /dev/null \
  -X POST -H 'content-type: application/json' \
  -d '{"isbn":"978-0262033848","uye":"U-4711"}' http://127.0.0.1:8432/odunc \
  --next -sS -o /dev/null http://127.0.0.1:8432/kitaplar
sleep 0.3

echo "### ayri baglantida bilinmeyen yol"
curl -sS -o /dev/null http://127.0.0.1:8432/raflar
sleep 0.3

kill "$sunucu"
```

```
yasam dongusu 127.0.0.1:8432
### tek baglanti uzerinde iki istek (curl baglantiyi yeniden kullanir)

#1.1  POST /odunc -> 201
      0.000 ms  baglanti kabulu
      1.378 ms  baslik satirlari cozuldu
      2.192 ms  govde okundu
      2.240 ms  yonlendirme -> oduncVer
      3.534 ms  isleyici bitti
      5.434 ms  baslik yazildi
      6.882 ms  yanit tamamlandi

#1.2  GET /kitaplar -> 200
      0.000 ms  baglanti kabulu
      7.580 ms  baslik satirlari cozuldu
      7.702 ms  govde okundu
      7.728 ms  yonlendirme -> katalogListele
      7.779 ms  isleyici bitti
      7.823 ms  baslik yazildi
      8.088 ms  yanit tamamlandi
### ayri baglantida bilinmeyen yol

#2.1  GET /raflar -> 404
      0.000 ms  baglanti kabulu
      0.180 ms  baslik satirlari cozuldu
      0.243 ms  govde okundu
      0.278 ms  yonlendirme -> yok
      0.280 ms  isleyici bitti
      0.334 ms  baslik yazildi
      0.541 ms  yanit tamamlandi
```

Süreler her çalıştırmada değişir; ilk isteğin ölçümleri modül yüklemesinin ve ilk dosya
okumasının ısınma maliyetini de taşır. Değişmeyen, aşamaların sırası ve aralarındaki
büyüklük ilişkisidir.

Üçüncü ölçüm bir taban çizgisi verir: eşleşmeyen bir yol için toplam yarım milisaniyedir ve
bu sürenin tamamı protokol işidir — bağlantı kabulü, ayrıştırma, yanıt yazımı. Uygulamanın
kendi işi sıfırdır. İlk ölçümde `isleyici bitti` ile `yonlendirme` arasındaki 1,3 milisaniye
ise gerçek iştir: dosya okuması ve iş kuralı. Bir isteğin yavaş olduğu söylendiğinde
sorulacak ilk soru, sürenin bu iki bölümden hangisinde geçtiğidir.

## Bağlantı ile İstek Ayrı Şeylerdir

İkinci ölçümün ilk satırı `0.000 ms baglanti kabulu` der, ama ikinci satırı `7.580 ms`
gösterir. Arada yeni bir bağlantı kabulü yoktur; ikinci istek, birinci isteğin bittiği
bağlantıdan gelmiştir. `#1.1` ve `#1.2` etiketleri bunu doğrudan söyler: aynı bağlantı,
ikinci istek. Üçüncü istek `#2.1` etiketiyle yeni bir bağlantı numarası almıştır.

Bu ayrım pratik sonuçlar taşır. **Bağlantı başına yapılan iş bir kez yapılır**: TCP el
sıkışması, güvenli bağlantı kurulumu ve buna bağlı gecikme, bağlantı açık kaldığı sürece
sonraki isteklere yayılır. Buna karşılık **bağlantıya bağlanan durum tehlikelidir**: bir
soket üzerinde birden çok üyenin isteği geçebilir, çünkü aradaki vekil bağlantıyı yeniden
kullanabilir. Kimlik bilgisi bağlantıya değil, isteğe aittir.

Ayrım kaynak yönetimini de belirler. Açık bağlantı sayısı, işlenen istek sayısından
bağımsızdır; hiç istek göndermeyen bir istemci de bir bağlantı tutar. Sunucunun bağlantı
sayısına ve boşta bekleme süresine ayrı sınır koymasının nedeni budur.

## Gövde Bir Değer Değil, Bir Akıştır

Kodda gövde `data` olaylarıyla parça parça toplanır ve `end` olayıyla tamamlanır. Bu, ölçüm
çıktısında `baslik satirlari cozuldu` ile `govde okundu` arasındaki aralık olarak görünür.
Küçük bir JSON gövdesinde bu aralık bir milisaniyenin altındadır; büyük bir yüklemede
saniyeler sürebilir.

Buradan iki karar çıkar. Birincisi, **yönlendirme gövdeden önce yapılabilir.** Bu örnekte
yönlendirme kararı gövde okunduktan sonra kaydedilmiştir, çünkü kod okunaklı olsun diye
işleyici mantığı tek yerde toplanmıştır. Yöntem ve yol ikinci aşamada zaten bilindiği için
yönlendirme oraya alınabilir; alındığında eşleşmeyen bir istek gövdesi hiç okunmadan
reddedilir.

İkincisi, **gövde sınırlanmalıdır.** Sunucu gövdeyi belleğe topladığı sürece, gönderilen
bayt sayısı sunucunun bellek tüketimini belirler. Sınır konmazsa tek bir istemci süreci
belleksiz bırakabilir. Sınırın nasıl konduğu ve aşıldığında hangi durum kodunun döndüğü,
bu kursun Dosya Yükleme ve Depolama dersinde ele alınır.

## Kararın Geri Alınamadığı An

Altıncı aşama bir eşiktir. Durum satırı ve başlıklar sokete yazıldıktan sonra istemci onları
almıştır; geri alınamaz. Bunu doğrulamak için yanıt yazımına başladıktan sonra başlık
eklemeyi ve durum kodunu değiştirmeyi deneyen bir sunucu yeterlidir.

```js
// baslik-sonrasi.mjs — durum satiri yazildiktan sonra kararin geri alinamadigini gosterir
import { createServer } from "node:http";

createServer((istek, yanit) => {
  yanit.sendDate = false;
  yanit.writeHead(201, { "content-type": "application/json; charset=utf-8" });
  yanit.write('{"durum":"odunc verildi"');            // govde akmaya basladi

  for (const [ad, deneme] of [
    ["setHeader", () => yanit.setHeader("x-uyari", "gec kalindi")],
    ["writeHead", () => yanit.writeHead(500)],
  ]) {
    try {
      deneme();
      console.log(`${ad}: gecti`);
    } catch (hata) {
      console.log(`${ad}: ${hata.code}`);
    }
  }

  yanit.end(',"not":"durum kodu 201 olarak kaldi"}');
}).listen(8433, "127.0.0.1", () => console.log("baslik sonrasi 127.0.0.1:8433"));
```

```bash
#!/usr/bin/env bash
# baslik-sonrasi.mjs'yi baslatir; yanitin basliklarini ve govdesini alir, sunucu kutugunu gosterir.
node baslik-sonrasi.mjs & sunucu=$!
sleep 0.6
curl -sS -D - http://127.0.0.1:8433/odunc
echo
kill "$sunucu"
```

```
baslik sonrasi 127.0.0.1:8433
setHeader: ERR_HTTP_HEADERS_SENT
writeHead: ERR_HTTP_HEADERS_SENT
HTTP/1.1 201 Created
content-type: application/json; charset=utf-8
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked

{"durum":"odunc verildi","not":"durum kodu 201 olarak kaldi"}
```

Sunucunun kütük satırları ile `curl` çıktısı aynı uçbirime yazıldığı için ilk iki satır
sunucudan, kalanı istemciden gelir.

İki deneme de aynı hata koduyla reddedilmiştir ve yanıt `201` olarak kalmıştır. Çıktıda
ayrıca `content-length` yerine `Transfer-Encoding: chunked` görünür: gövde parça parça
yazıldığı için sunucu uzunluğu önceden bildirememiştir.

Sonucun kural hâline gelmiş biçimi şudur: **yanıtla ilgili bütün kararlar ilk baytı yazmadan
önce alınmış olmalıdır.** İşleyicinin ortasında yazmaya başlayıp sonra bir hatayla
karşılaşan kod, o hatayı istemciye bildiremez — yarım bir gövde ve yanlış bir durum kodu
kalır. Yanıt gövdesini önce bellekte üretip sonra tek seferde yazmanın, ya da akışlı
gönderimde hata olasılığını akış başlamadan tüketmenin nedeni budur.

## Özet

- Bir isteğin yolu yedi aşamadan geçer: bağlantı kabulü, başlıkların çözülmesi, gövdenin
  okunması, yönlendirme, işleyicinin çalışması, başlığın yazılması ve yanıtın tamamlanması.
- Ölçümde eşleşmeyen bir yol yarım milisaniyede tamamlanmıştır; bu süre protokol işidir.
  Uygulamanın kendi işi, yönlendirme ile işleyicinin bitişi arasındaki aralıkta görünür.
- Bağlantı ile istek ayrı yaşam sürelerine sahiptir: aynı bağlantı üzerindeki ikinci istek
  yeni bir kabul aşaması içermez, bu yüzden kimlik ve durum bağlantıya değil isteğe bağlanır.
- Gövde parça parça gelir; yönlendirme gövdeden önce yapılabilir ve gövdenin boyutu
  sınırlanmadığında sunucunun bellek tüketimini istemci belirler.
- Başlıklar yazıldıktan sonra başlık eklemek ve durum kodunu değiştirmek
  `ERR_HTTP_HEADERS_SENT` ile reddedilir; yanıtla ilgili kararlar ilk bayttan önce alınır.

## Sonraki Adım

Ölçülen aşamaların hepsi tek bir işleyicinin içindeydi ve her aşama koda elle yazılmıştı.
Gerçek bir uygulamada aynı işler — kimliğin çözülmesi, gövdenin ayrıştırılması, isteğin
kütüğe yazılması, hatanın yakalanması — her işleyicide tekrarlanır. Bu tekrarları tek bir
yerde toplayan yapı, isteği sırayla geçiren bir zincirdir. Sonraki ders o zinciri kurar:
halkaların sırası değiştiğinde sonucun nasıl değiştiğini, kimlik denetimiyle günlüklemenin
hangi sırayla dizilmesi gerektiğini ve hata yakalayıcının zincirin neresinde durduğunu
ölçerek gösterir.
