---
title: 'Web Sunucusu Kavramı'
source: 'https://academia.sh/tr/kurslar/sunucu-temelleri/web-sunucusu-kavrami'
course: 'Sunucu Tarafı Temelleri'
language: tr
updated: '2026-08-17T18:06:52+00:00'
license: 'CC BY-SA 4.0'
---

# Web Sunucusu Kavramı

Sunucu tarafının üç ayrı rolü; dosyaları doğru başlıklarla sunmak, istekleri yola göre ayırmak ve isteği arka bir sunucuya devretmek ayrı süreçler olarak kurulup ölçülür.

Önceki iki derste "sunucu" tek bir program gibi ele alındı: bağlantı noktasını dinleyen,
isteği okuyan, kuralı uygulayan ve yanıtı yazan tek süreç. Kütüphane uygulamasının gerçek
trafiği ise türdeş değildir. Aynı bağlantı noktasına hem `/stil.css` isteği hem
`/api/kitaplar` isteği gelir; birincisi diskteki bir dosyanın olduğu gibi verilmesidir,
ikincisi bir hesabın sonucudur.

Bu ders o ayrımı üç role böler ve her rolü ayrı bir süreç olarak çalıştırır: **statik
sunum**, **yönlendirme** ve **vekillik**. Roller ayrı ayrı kurulduğunda hangi işin hangi
bileşene ait olduğu görünür hâle gelir; tek bir programda toplandıklarında da aynı roller
vardır, yalnız sınırları görünmez.

## Statik Sunum: Dosyayı Bulmak ve Doğru Başlıkla Vermek

Statik sunum, bir isteğin yolunu dosya sistemindeki bir dosyaya eşleyip içeriği olduğu gibi
göndermektir. Görünüşte tek satırlık bir iştir; sorumluluklar sayıldığında dört tanedir.

```js
// statik-sunucu.mjs — varliklar/ dizinindeki dosyalari sunar, disina cikilmasini engeller
import { createServer } from "node:http";
import { readFile } from "node:fs/promises";
import { join, normalize, extname, sep } from "node:path";

const KOK = join(process.cwd(), "varliklar");
const TIPLER = { ".html": "text/html; charset=utf-8", ".css": "text/css; charset=utf-8",
  ".js": "text/javascript; charset=utf-8", ".json": "application/json; charset=utf-8" };

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  yanit.setHeader("x-sunan", "statik");
  const yol = decodeURIComponent(new URL(istek.url, "http://yerel").pathname);
  const tam = normalize(join(KOK, yol === "/" ? "/index.html" : yol));

  if (!tam.startsWith(KOK + sep)) {                       // kok disina cikan yol reddedilir
    return yanit.writeHead(403, { "content-type": "text/plain" }).end("kok disi\n");
  }
  try {
    const icerik = await readFile(tam);
    yanit.writeHead(200, {
      "content-type": TIPLER[extname(tam)] ?? "application/octet-stream",
      "content-length": icerik.length,
      "cache-control": "public, max-age=31536000, immutable",
    }).end(icerik);
  } catch {
    yanit.writeHead(404, { "content-type": "text/plain" }).end("bulunamadi\n");
  }
}).listen(8427, "127.0.0.1", () => console.log("statik 127.0.0.1:8427"));
```

Örneği çalıştırmak için sunucu dosyasının yanında bir `varliklar` dizini ve içinde iki dosya
bulunmalıdır:

```bash
mkdir -p varliklar
printf 'body { font-family: system-ui; }\n.kitap { padding: 8px; }\n' > varliklar/stil.css
printf '<!doctype html>\n<html lang="tr"><head><meta charset="utf-8"><title>Kutuphane</title>\n<link rel="stylesheet" href="/stil.css"></head>\n<body><h1>Kutuphane Odunc Servisi</h1></body></html>\n' > varliklar/index.html
```

```bash
#!/usr/bin/env bash
# statik-sunucu.mjs'yi baslatir, dort istegi dener, durdurur. varliklar/ dizini yaninda olmalidir.
node statik-sunucu.mjs & sunucu=$!
sleep 0.5
for yol in / /stil.css /yok.css '/%2e%2e%2f%2e%2e%2fetc%2fhosts'; do
  printf '%-32s -> ' "$yol"
  curl -sS -o /dev/null --path-as-is -w '%{http_code}  tip=%{content_type}  boy=%{size_download} B\n' \
    "http://127.0.0.1:8427$yol"
done
echo "--- yanit basliklari ---"
curl -sS -o /dev/null -D - --path-as-is http://127.0.0.1:8427/stil.css
kill "$sunucu"
```

```
statik 127.0.0.1:8427
/                                -> 200  tip=text/html; charset=utf-8  boy=186 B
/stil.css                        -> 200  tip=text/css; charset=utf-8  boy=58 B
/yok.css                         -> 404  tip=text/plain  boy=11 B
/%2e%2e%2f%2e%2e%2fetc%2fhosts   -> 403  tip=text/plain  boy=9 B
--- yanit basliklari ---
HTTP/1.1 200 OK
x-sunan: statik
content-type: text/css; charset=utf-8
content-length: 58
cache-control: public, max-age=31536000, immutable
Connection: keep-alive
Keep-Alive: timeout=5
```

Dört sorumluluk çıktıda ayrı ayrı görünür. **Yol eşleme**: `/` isteği dizin girişine
karşılık gelir. **İçerik tipi**: aynı sunucu iki farklı tip üretmiştir; tip yanlış olduğunda
tarayıcı dosyayı yorumlayamaz ve stil uygulanmaz. **Bulunamayan kaynak**: eksik dosya bir
çökme değil, `404` durum kodudur. **Kök sınırı**: son istek `403` almıştır.

Son satır ayrıca bir uyarı taşır. `new URL()` yapısı yol üzerindeki `..` bileşenlerini kendi
kendine sadeleştirir; bu yüzden düz bir `/../../etc/hosts` isteği sunucuya ulaşmadan
temizlenir. Kodlanmış biçim (`%2e%2e%2f`) ise sadeleşmeden geçer ve `decodeURIComponent`
sonrası dizin dışına çıkan bir yola dönüşür. Denetimin çözümlenmiş **mutlak yol** üzerinde,
kök dizinle karşılaştırılarak yapılmasının nedeni budur: yolun metin hâline bakan bir
denetim, kodlamanın her biçimini önceden saymak zorunda kalır.

Yanıttaki `cache-control` başlığı, statik sunumun ayırt edici özelliğini taşır: içerik
isteğe göre değişmediği için uzun süreli saklanabilir. Bu başlığın anlamı ve ödünleşimleri
Tarayıcı ve Web Platformu kursunda kurulmuştu; burada önemli olan, aynı başlığın dinamik
bir yanıta konulamayacağıdır.

## Yönlendirme ve Vekillik: İsteği Doğru Bileşene Götürmek

Statik sunucu `/api/kitaplar` isteğine ne yapacağını bilmez; ödünç uygulaması da CSS
dosyasını okumakla uğraşmamalıdır. Aradaki iş bölümünü kuran bileşen **ters vekildir**
(reverse proxy): istemciden gelen isteği kabul eder, bir kurala göre arka sunuculardan
birine iletir ve gelen yanıtı istemciye geri yazar.

```js
// uygulama-sunucu.mjs — odunc uygulamasi; istegin kendisine hangi baglantidan geldigini bildirir
import { createServer } from "node:http";

const KITAPLAR = [{ isbn: "978-0262033848", ad: "Algoritmalara Giris", raf: "R-12" }];

createServer((istek, yanit) => {
  yanit.sendDate = false;
  yanit.setHeader("x-sunan", "uygulama");
  yanit.setHeader("content-type", "application/json; charset=utf-8");
  yanit.writeHead(200).end(JSON.stringify({
    yol: istek.url,
    kitaplar: KITAPLAR,
    baglantiSahibi: `${istek.socket.remoteAddress}:${istek.socket.remotePort}`,
    forwarded: istek.headers.forwarded ?? null,
  }));
}).listen(8428, "127.0.0.1", () => console.log("uygulama 127.0.0.1:8428"));
```

```js
// vekil.mjs — one duran ters vekil: yola gore iki arka sunucudan birine iletir
import { createServer, request } from "node:http";

const HEDEF = (yol) => (yol.startsWith("/api/") ? 8428 : 8427);

createServer((istekDisi, yanitDisi) => {
  yanitDisi.sendDate = false;
  const kaynak = istekDisi.socket.remoteAddress;
  const basliklar = { ...istekDisi.headers, forwarded: `for="${kaynak}";proto=http` };

  const istekIc = request(
    { host: "127.0.0.1", port: HEDEF(istekDisi.url), path: istekDisi.url,
      method: istekDisi.method, headers: basliklar },
    (yanitIc) => {
      yanitDisi.writeHead(yanitIc.statusCode, { ...yanitIc.headers, "x-vekil": "gecti" });
      yanitIc.pipe(yanitDisi);
    },
  );
  istekIc.on("error", () => yanitDisi.writeHead(502).end("arka sunucuya ulasilamadi\n"));
  istekDisi.pipe(istekIc);
}).listen(8429, "127.0.0.1", () => console.log("vekil 127.0.0.1:8429"));
```

```bash
#!/usr/bin/env bash
# Uc sunucuyu birlikte baslatir: statik (8427), uygulama (8428), vekil (8429).
node statik-sunucu.mjs & s1=$!
node uygulama-sunucu.mjs & s2=$!
node vekil.mjs & s3=$!
sleep 0.6

echo "--- vekil uzerinden: hangi arka sunucu yanitladi ---"
for yol in /stil.css /api/kitaplar; do
  printf '%-16s -> ' "$yol"
  curl -sS -o /dev/null -D - "http://127.0.0.1:8429$yol" | grep -iE '^(HTTP|x-sunan|x-vekil)' | tr -d '\r' | paste -sd' ' -
done

echo "--- uygulamanin gordugu baglanti: dogrudan istek ve vekil uzerinden istek ---"
printf 'dogrudan : '; curl -sS http://127.0.0.1:8428/api/kitaplar | tr ',' '\n' | grep -E 'baglantiSahibi|forwarded' | paste -sd' ' -
printf 'vekille  : '; curl -sS http://127.0.0.1:8429/api/kitaplar | tr ',' '\n' | grep -E 'baglantiSahibi|forwarded' | paste -sd' ' -

echo "--- ek atlamanin suresi (ayni yanit, iki yol) ---"
curl -sS -o /dev/null -w 'dogrudan : %{time_total} s\n' http://127.0.0.1:8428/api/kitaplar
curl -sS -o /dev/null -w 'vekille  : %{time_total} s\n' http://127.0.0.1:8429/api/kitaplar

kill "$s1" "$s2" "$s3"
```

```
uygulama 127.0.0.1:8428
vekil 127.0.0.1:8429
statik 127.0.0.1:8427
--- vekil uzerinden: hangi arka sunucu yanitladi ---
/stil.css        -> HTTP/1.1 200 OK x-sunan: statik x-vekil: gecti
/api/kitaplar    -> HTTP/1.1 200 OK x-sunan: uygulama x-vekil: gecti
--- uygulamanin gordugu baglanti: dogrudan istek ve vekil uzerinden istek ---
dogrudan : "baglantiSahibi":"127.0.0.1:57388" "forwarded":null}
vekille  : "baglantiSahibi":"127.0.0.1:57387" "forwarded":"for=\"127.0.0.1\";proto=http"}
--- ek atlamanin suresi (ayni yanit, iki yol) ---
dogrudan : 0.000551 s
vekille  : 0.000962 s
```

Üç sunucunun açılış satırlarının çıktıdaki sırası çalıştırmadan çalıştırmaya değişir; geçici
bağlantı noktası numaraları ve süre alanları da her çalıştırmada farklıdır.

İstemci tek bir adrese, 8429'a istek yapmıştır. `x-sunan` başlığı iki isteğin iki ayrı
programdan geldiğini gösterir. İstemci açısından bu ayrım görünmez: tek köken, tek adres,
tek yapılandırma. Yönlendirme kuralı burada tek satırdır — yol `/api/` ile başlıyorsa
uygulamaya, başlamıyorsa statik sunucuya.

## Vekilin Getirdiği ve Götürdüğü

Vekil ücretsiz değildir. Ölçümdeki iki süre, aynı yanıt için doğrudan bağlantı ile vekil
üzerinden geçen bağlantıyı karşılaştırır: vekilli yol daha uzundur, çünkü ikinci bir TCP
bağlantısı kurulur, istek yeniden yazılır ve yanıt yeniden aktarılır. Geri döngü arayüzünde
bu fark küçüktür; gerçek bir dağıtımda vekil ile arka sunucu arasındaki ağ da hesaba
katılır.

Karşılığında birkaç iş tek noktada toplanır. **Şifreli bağlantı önde sonlandırılabilir**;
arka sunucular düz HTTP konuşur ve sertifika yönetimi tek bileşene iner. **Aynı yol kuralı
birden çok arka örneğe dağıtım yapabilir**: hedef seçimi tek bir bağlantı noktası yerine bir
liste üzerinden yapıldığında vekil bir yük dağıtıcıya dönüşür. **Statik varlıklar uygulama
sürecine hiç uğramaz**, bu yüzden uygulamanın eşzamanlılık bütçesi yalnız hesaplı isteklere
harcanır.

Bedelin bir kısmı ise bilgi kaybıdır ve çıktıda görünür. Uygulama, isteği kimin açtığını
`istek.socket` üzerinden okur; vekil devreye girdiğinde bu bağlantıyı açan taraf **vekildir**,
istemci değil. Ölçüm aynı makinede yapıldığı için iki satırdaki adres de `127.0.0.1`'dir;
ayırt edici olan, bağlantının sahibinin değişmiş olmasıdır. İstemcinin kim olduğu bilgisi
ancak vekilin eklediği `Forwarded` başlığıyla taşınır ve çıktıda yalnız vekilli satırda
doludur.

Bu başlık bir **iddiadır**, ölçüm değil. İstemci de aynı başlığı gönderebilir. Bu yüzden
uygulama, `Forwarded` başlığını yalnız bilinen bir vekilden geldiğini doğruladığı
bağlantılarda dikkate alır; doğrudan erişime açık bir uygulamada başlık yok sayılır. Aynı
akıl yürütme ikinci derste kurulmuştu: istemciden gelen hiçbir değer, sunucunun kendi
denetiminde olmadığı sürece karar sayılmaz.

## Roller mi, Programlar mı?

Üç dosya üç ayrı süreç başlattı, ama bu bir zorunluluk değil bir gösterim tercihidir. Aynı
üç rol tek bir programda da bulunabilir: uygulama, `/api/` ile başlamayan yolları kendi
dosya okuma dalına yönlendirebilir. Ayrımın kalıcı olan yanı programların sayısı değil,
sorumlulukların içeriğidir.

- **Web sunucusu rolü**: bağlantıyı kabul eder, HTTP iletisini ayrıştırır, statik içeriği
  doğrudan sunar, yolları hedeflere eşler. Bu rolün bilmediği şey iş kurallarıdır.
- **Uygulama sunucusu rolü**: isteği bir işleyiciye taşır, iş kuralını çalıştırır, yanıt
  gövdesini üretir. Bu rolün bilmediği şey dosya sisteminin düzenidir.
- **Vekillik rolü**: iki tarafı birbirine bağlar, kaynak bilgisini taşır, hedefler arasında
  dağıtım yapar, arka sunucu erişilemediğinde `502` üretir.

Rolleri ayırmanın pratik sonucu, her birinin ayrı ölçeklenebilmesidir: statik trafiği artan
bir sistem statik tarafı çoğaltır, hesaplı istekleri artan bir sistem uygulama tarafını.
Rollerin tek bir programda toplandığı bir kurulumda bu iki ihtiyaç birbirine bağlanır ve
ikisi birden çoğaltılır.

## Özet

- Statik sunum dört sorumluluk taşır: yolun dosyaya eşlenmesi, doğru içerik tipi, eksik
  dosyanın `404` ile karşılanması ve kök dizin sınırının çözümlenmiş mutlak yol üzerinden
  denetlenmesi.
- Adres çözümlemesi `..` bileşenlerini sadeleştirdiği için yol denetimi metin üzerinde değil,
  birleştirilmiş ve normalleştirilmiş yolun kök dizinle karşılaştırılmasıyla yapılır.
- Ters vekil, tek bir adrese gelen istekleri bir kurala göre arka sunuculara dağıtır;
  ölçümde `x-sunan` başlığı aynı adrese yapılan iki isteğin iki ayrı programdan geldiğini
  gösterir.
- Vekil bir atlama ekler ve bağlantının sahibini değiştirir; istemcinin kimliği ancak
  `Forwarded` başlığıyla taşınır ve bu başlık yalnız bilinen bir vekilden geldiğinde
  dikkate alınır.
- Web sunucusu, uygulama sunucusu ve vekillik birer roldür; ayrı süreçlere bölünmeleri
  zorunlu değildir, ama ayrı ölçeklenebilmelerini bu bölünme sağlar.

## Sonraki Adım

Vekilin arkasındaki uygulama sunucusu bu derste tek bir süreç olarak çalıştı ve istekleri
sırayla karşıladı. Bir istek uzun sürdüğünde diğerlerine ne olur? Aynı uygulamanın iki
kopyası aynı bağlantı noktasını nasıl paylaşır ve istekler aralarında nasıl dağılır?
Ölçekleme kararının birimi süreç müdür, iş parçacığı mıdır, yoksa başka bir şey midir?
Sonraki ders uygulamanın altındaki çalışma zamanına iner: olay döngüsünün tek bir isteği
nasıl beklettiğini ölçer, sonra aynı bağlantı noktasını paylaşan işçi süreçler başlatıp
isteklerin dağılımını sayar.
