---
title: 'Sahte Sunucular'
source: 'https://academia.sh/tr/kurslar/api-tasarimi/sahte-sunucular'
course: 'Web API Tasarımı'
language: tr
updated: '2026-08-17T18:06:44+00:00'
license: 'CC BY-SA 4.0'
---

# Sahte Sunucular

Şemadan örnek yanıt üreten bir sunucunun yazılması, istemcinin ona karşı geliştirilmesi, aynı kodun gerçek sunucuya geçtiğinde çalıştığının ölçülmesi ve sahte sunucunun gizlediği dalların sayılması.

Beklenti dosyaları şu ana kadar tek yönlü kullanıldı: tüketici yazdı, sağlayıcı sınadı. Aynı
sözleşme ters yönde de iş görür. Yeni bir tüketici geliştirilmeye başlandığında sağlayıcı o
uç noktayı henüz yazmamış olabilir; tüketicinin beklemesi ise gereksizdir, çünkü sözleşme
zaten bellidir.

**Sahte sunucu** (mock server) sözleşmeyi çalıştırılabilir hâle getirir: tanımdaki şemadan
örnek yanıtlar üretir ve gerçek sunucunun yerine geçer. Bu ders böyle bir sunucu yazar,
istemciyi ona karşı geliştirir, sonra aynı kodu gerçek sunucuya bağlayarak iki şeyi ölçer —
yanıtın biçimi aynı mı ve istemcinin hangi dalları hiç çalışmamış.

## Şemaya Örnek Eklemek

Örnek üretmek her alan için mümkün değildir. Tip bilgisi bir dizgi üretmeye yeter ama
`^U-\d{4}$` desenine uyan bir dizgi üretmeye yetmez. Bu yüzden deseni olan alanlar örnek
taşımak zorundadır; JSON Schema bunun için `examples` sözcüğünü tanımlar.

```js
// tanim.mjs — odunc servisinin makine okunur tanimi (ornek degerlerle)
// Deseni olan her alan bir ornek tasir; sahte sunucu bunlari kullanir.
const ODUNC_ISTEK = {
  type: "object", required: ["uye", "kalemler"], additionalProperties: false,
  properties: {
    uye: { type: "string", pattern: "^U-\\d{4}$", examples: ["U-1001"] },
    kalemler: { type: "array", items: { type: "object", required: ["isbn"], additionalProperties: false,
      properties: { isbn: { type: "string", pattern: "^97[89]-\\d{10}$", examples: ["978-0262033848"] } } } },
    sube: { type: "string", enum: ["merkez", "sahil", "tepe"] },
  },
};

const ODUNC_YANIT = {
  type: "object", required: ["id", "uye", "kalemler", "iadeTarihi", "durum"], additionalProperties: false,
  properties: {
    id: { type: "string", pattern: "^O-\\d+$", examples: ["O-1"] },
    uye: { type: "string", pattern: "^U-\\d{4}$", examples: ["U-1001"] },
    kalemler: { type: "array", items: { type: "object", required: ["isbn"], additionalProperties: false,
      properties: { isbn: { type: "string", pattern: "^97[89]-\\d{10}$", examples: ["978-0262033848"] } } } },
    iadeTarihi: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", examples: ["2026-04-15"] },
    durum: { type: "string", enum: ["acik", "kapali"] },
  },
};

const SORUN = {
  type: "object", required: ["type", "title", "status", "detail", "instance"],
  properties: {
    type: { type: "string", examples: ["https://ornek.kutuphane/sorunlar/ornek"] },
    title: { type: "string", examples: ["Örnek sorun"] },
    status: { type: "integer", examples: [0] },
    detail: { type: "string", examples: ["Örnek açıklama."] },
    instance: { type: "string", examples: ["ol-0001"] },
    errors: { type: "array", items: { type: "object", required: ["yol", "kod"],
      properties: { yol: { type: "string", examples: ["/uye"] }, kod: { type: "string", examples: ["bicim"] } } } },
  },
};

export const TANIM = {
  "POST /odunc": { istek: ODUNC_ISTEK, yanit: { 201: ODUNC_YANIT, 422: SORUN } },
  "GET /odunc/{id}": { istek: null, yanit: { 200: ODUNC_YANIT, 404: SORUN } },
};
```

```js
// ornek.mjs — semadan ornek deger uretir
// Kural: enum varsa ilk deger, examples varsa ilk ornek, desen varsa ornek zorunlu.
export function ornekUret(sema, yol = "") {
  if (sema.enum) return sema.enum[0];
  if (sema.examples?.length) return sema.examples[0];
  if (sema.pattern) throw new Error(`${yol || "/"}: deseni olan alan için examples yazılmamış`);
  switch (sema.type) {
    case "string":  return "metin";
    case "integer": case "number": return 0;
    case "boolean": return false;
    case "array":   return sema.items ? [ornekUret(sema.items, `${yol}/0`)] : [];
    case "object": {
      const nesne = {};
      for (const [ad, alt] of Object.entries(sema.properties ?? {})) nesne[ad] = ornekUret(alt, `${yol}/${ad}`);
      return nesne;
    }
    default: return null;
  }
}
```

Desen varken örnek yoksa üretici hata verir. Bu, sözleşmeye konan yeni bir koşuldur:
**şema, kendisinden örnek üretilebilecek kadar eksiksiz olmalıdır.**

```js
// eksik-ornek.mjs — ornegi olmayan desenli alan sahte sunucuyu ayaga kaldirmaz
import { ornekUret } from "./ornek.mjs";

const TAM = { type: "object", required: ["barkod"],
  properties: { barkod: { type: "string", pattern: "^BK-\\d{6}$", examples: ["BK-004312"] } } };
const EKSIK = { type: "object", required: ["barkod"],
  properties: { barkod: { type: "string", pattern: "^BK-\\d{6}$" } } };

console.log("örnekli şema ->", JSON.stringify(ornekUret(TAM)));
try { ornekUret(EKSIK); } catch (h) { console.log("örneksiz şema ->", h.message); }
```

```
örnekli şema -> {"barkod":"BK-004312"}
örneksiz şema -> /barkod: deseni olan alan için examples yazılmamış
```

## Sahte Sunucu

Sahte sunucu tanımdaki yolları tanır, örnekleri **bir kez** üretir ve döndürür. Örnekler
başlangıçta üretildiği için eksik örnek sorunu sunucu ayağa kalkarken görülür, ilk istekte
değil.

```js
// sahte.mjs — tanimdan ornek yanit ureten sunucu
// Kullanim: node sahte.mjs <port> [sorun]   "sorun": hata yollarini da uretir
import { createServer } from "node:http";
import { TANIM } from "./tanim.mjs";
import { ornekUret } from "./ornek.mjs";

const PORT = Number(process.argv[2] ?? 8439);
const SORUN_URET = process.argv[3] === "sorun";

// Ornekler bir kez uretilir; deseni olup ornegi olmayan alan burada hata verir.
const ORNEKLER = Object.fromEntries(
  Object.entries(TANIM).flatMap(([a, t]) =>
    Object.entries(t.yanit).map(([k, s]) => [`${a} ${k}`, ornekUret(s)])));

const govdeOku = (istek) =>
  new Promise((coz) => { let v = ""; istek.on("data", (p) => (v += p)); istek.on("end", () => coz(v)); });

createServer(async (istek, yanit) => {
  yanit.sendDate = false;
  const yol = istek.url.split("?")[0];
  const anahtar = istek.method === "POST" && yol === "/odunc" ? "POST /odunc"
    : istek.method === "GET" && /^\/odunc\/[^/]+$/.test(yol) ? "GET /odunc/{id}" : null;

  if (!anahtar) { yanit.writeHead(404).end(); return; }
  await govdeOku(istek);

  // Hangi durum kodunun uretilecegi: basari, ya da sorun kipinde hata kodu.
  const kodlar = Object.keys(TANIM[anahtar].yanit).map(Number);
  const basari = kodlar.find((k) => k < 400);
  const kod = SORUN_URET && istek.headers["ornek-sorun"] ? Number(istek.headers["ornek-sorun"]) : basari;

  const govde = structuredClone(ORNEKLER[`${anahtar} ${kod}`]);
  if (govde && kod >= 400) govde.status = kod;          // ornek govdeye gercek kod yazilir
  yanit.writeHead(kod, { "content-type": kod >= 400 ? "application/problem+json; charset=utf-8" : "application/json; charset=utf-8" });
  yanit.end(JSON.stringify(govde));
}).listen(PORT, "127.0.0.1", () => console.log(`sahte sunucu 127.0.0.1:${PORT}${SORUN_URET ? " (sorun kipi)" : ""}`));
```

İstemci, sunucu adresini dışarıdan alır. Sahte ve gerçek sunucu arasındaki geçiş bu tek
bağımlılığın değişmesiyle olur; başka hiçbir şey değişmez.

```js
// istemci.mjs — odunc kaydini gosteren istemci; sunucu adresi disaridan verilir
export async function oduncGoster(taban, id, ekBaslik = {}) {
  const cevap = await fetch(`${taban}/odunc/${id}`, { headers: ekBaslik });
  const govde = await cevap.json();
  const alanlar = Object.keys(govde).join(",");
  if (cevap.status === 200)
    return { dal: "başarı", alanlar, satir: `${govde.id} · ${govde.uye} · ${govde.kalemler.length} kalem · ${govde.iadeTarihi} · ${govde.durum}` };
  if (cevap.status === 404) return { dal: "bulunamadı", alanlar, satir: govde.detail };
  if (cevap.status === 422) return { dal: "doğrulama", alanlar, satir: govde.errors.map((h) => `${h.yol}=${h.kod}`).join(", ") };
  return { dal: "bilinmeyen", alanlar, satir: `beklenmeyen kod ${cevap.status}` };
}
```

```js
// gercek.mjs — tanima uyan gercek sunucu
import { createServer } from "node:http";

const KAYITLAR = new Map([["O-1", { id: "O-1", uye: "U-1001", kalemler: [{ isbn: "978-0201896831" }], iadeTarihi: "2026-05-02", durum: "kapali" }]]);

createServer((istek, yanit) => {
  yanit.sendDate = false;
  const id = istek.url.split("?")[0].replace("/odunc/", "");
  const kayit = KAYITLAR.get(id);
  if (kayit) {
    yanit.writeHead(200, { "content-type": "application/json; charset=utf-8" });
    return yanit.end(JSON.stringify(kayit));
  }
  yanit.writeHead(404, { "content-type": "application/problem+json; charset=utf-8" });
  yanit.end(JSON.stringify({ type: "https://ornek.kutuphane/sorunlar/kaynak-yok", title: "Kaynak bulunamadı",
    status: 404, detail: `${id} numaralı ödünç kaydı yok.`, instance: "ol-0007" }));
}).listen(8440, "127.0.0.1", () => console.log("gercek sunucu 127.0.0.1:8440"));
```

## Aynı Kod, Üç Sunucu

Aşağıdaki koşucu istemciyi verilen adrese karşı çalıştırır, dönen gövdenin alan kümesini
yazar ve istemcinin kaç ayrı dalının çalıştığını sayar.

```js
// kosu.mjs — ayni istemci kodunu verilen adrese karsi calistirir, hangi dallarin
// calistigini ve yanitin bicimini bildirir.
// Kullanim: node kosu.mjs <taban-adres> [sorun]
import { oduncGoster } from "./istemci.mjs";

const TABAN = process.argv[2];
const SORUN = process.argv[3] === "sorun";

const DURUMLAR = SORUN
  ? [["var olan kayıt", "O-1", {}], ["olmayan kayıt", "O-9", { "ornek-sorun": "404" }]]
  : [["var olan kayıt", "O-1", {}], ["olmayan kayıt", "O-9", {}]];

const dallar = new Set();
for (const [ad, id, baslik] of DURUMLAR) {
  const s = await oduncGoster(TABAN, id, baslik);
  dallar.add(s.dal);
  console.log(`${ad.padEnd(16)} dal=${s.dal.padEnd(12)} ${s.satir}`);
  console.log(`${"".padEnd(16)} alanlar: ${s.alanlar}`);
}
console.log(`çalışan dal sayısı: ${dallar.size}/4  (${[...dallar].join(", ")})`);
```

```bash
#!/usr/bin/env bash
# Ayni istemci kodu: once sahte sunucuya, sonra gercek sunucuya.
node sahte.mjs 8439 & sahte=$!
node gercek.mjs & gercek=$!
sleep 0.6

echo "== sahte sunucu (yalniz basari ornekleri) =="
node kosu.mjs http://127.0.0.1:8439
kill "$sahte"; wait "$sahte" 2>/dev/null

node sahte.mjs 8439 sorun & sahte=$!
sleep 0.5
echo "== sahte sunucu (sorun kipi) =="
node kosu.mjs http://127.0.0.1:8439 sorun
kill "$sahte"; wait "$sahte" 2>/dev/null

echo "== gercek sunucu =="
node kosu.mjs http://127.0.0.1:8440
kill "$gercek"; wait "$gercek" 2>/dev/null
```

```
gercek sunucu 127.0.0.1:8440
sahte sunucu 127.0.0.1:8439
== sahte sunucu (yalniz basari ornekleri) ==
var olan kayıt   dal=başarı       O-1 · U-1001 · 1 kalem · 2026-04-15 · acik
                 alanlar: id,uye,kalemler,iadeTarihi,durum
olmayan kayıt    dal=başarı       O-1 · U-1001 · 1 kalem · 2026-04-15 · acik
                 alanlar: id,uye,kalemler,iadeTarihi,durum
çalışan dal sayısı: 1/4  (başarı)
sahte sunucu 127.0.0.1:8439 (sorun kipi)
== sahte sunucu (sorun kipi) ==
var olan kayıt   dal=başarı       O-1 · U-1001 · 1 kalem · 2026-04-15 · acik
                 alanlar: id,uye,kalemler,iadeTarihi,durum
olmayan kayıt    dal=bulunamadı   Örnek açıklama.
                 alanlar: type,title,status,detail,instance,errors
çalışan dal sayısı: 2/4  (başarı, bulunamadı)
== gercek sunucu ==
var olan kayıt   dal=başarı       O-1 · U-1001 · 1 kalem · 2026-05-02 · kapali
                 alanlar: id,uye,kalemler,iadeTarihi,durum
olmayan kayıt    dal=bulunamadı   O-9 numaralı ödünç kaydı yok.
                 alanlar: type,title,status,detail,instance
çalışan dal sayısı: 2/4  (başarı, bulunamadı)
```

Üçüncü bölüm asıl iddiayı doğruluyor: sahte sunucuya karşı geliştirilen istemci kodu,
gerçek sunucuya bağlandığında **değişmeden** çalıştı. Başarı gövdesinin alan kümesi iki
sunucuda birebir aynı; değerler farklı, biçim aynı. İstemcinin gerçek sunucuya taşınması
için değişen tek şey adres oldu.

Birinci bölüm sahte sunucunun tuzağını gösteriyor. Yalnız başarı örnekleri üreten sahte
sunucu, olmayan bir kaydı sorduğunuzda bile 200 döndürür. İstemcinin dört dalından yalnız
biri çalışır ve geliştirici hata yollarını hiç görmeden işini bitmiş sayar. Bu, gerçek
sunucuya geçişte en sık görülen kırılma nedenidir: eksik olan gövde biçimi değil, hiç
yazılmamış hata dalıdır.

İkinci bölüm çözümü veriyor. Sahte sunucu hangi durum kodunu üreteceğini istekten
öğrenebildiğinde, istemcinin bütün yolları geliştirme sırasında çalıştırılabilir. Çalışan
dal sayısı bire karşı iki oldu ve gerçek sunucuyla aynı sayıya ulaştı. Sözleşmede tanımlı
her durum kodunun sahte sunucudan istenebilmesi, sahte sunucuyu bir gösteri aracı olmaktan
çıkarıp bir geliştirme aracı yapar.

## Sahte Sunucunun Cömertliği

İkinci ve üçüncü bölümdeki 404 gövdelerinin alan kümeleri farklı: sahte sunucu `errors`
alanını da üretmiş, gerçek sunucu üretmemiş. İkisi de sözleşmeye uygundur, çünkü `errors`
zorunlu alanlar arasında değildir.

Buradaki risk tek yönlüdür. Sahte sunucu **isteğe bağlı alanları da** ürettiği için,
istemci onların her zaman geleceğini varsayabilir. Gerçek sunucuya geçişte o varsayım
çöker. Ters yönde bir risk yoktur: sahte sunucunun ürettiği zorunlu alanlar gerçek
sunucuda da vardır.

Bu yüzden örnek üreticinin isteğe bağlı alanları üretip üretmeyeceği bir seçenek olmalıdır.
İki kipte de çalıştırılan bir istemci, yalnız zorunlu alanlarla da ayakta kalabildiğini
göstermiş olur — sözleşmenin gerçekten söylediği budur.

## Özet

- Sahte sunucu, sözleşmeyi çalıştırılabilir hâle getirir ve tüketicinin sağlayıcıyı
  beklemeden geliştirmesini sağlar.
- Deseni olan alanlardan örnek üretilemez; şema, kendisinden örnek üretilebilecek kadar
  eksiksiz olmalıdır ve eksiklik sunucu ayağa kalkarken görülür.
- Aynı istemci kodu sahte ve gerçek sunucuda değişmeden çalışır; başarı gövdesinin alan
  kümesi ikisinde aynıdır, yalnız değerler farklıdır.
- Yalnız başarı örneği üreten sahte sunucu istemcinin dört dalından birini çalıştırır ve
  hata yollarını hiç yazılmamış bırakır.
- Sahte sunucudan istenen durum kodunun seçilebilmesi, çalışan dal sayısını gerçek
  sunucuyla aynı düzeye çıkarır.
- Sahte sunucu isteğe bağlı alanları da ürettiği için istemciyi onların hep geleceğine
  inandırabilir; üreticinin yalnız zorunlu alanlarla çalışan bir kipi de bulunmalıdır.

## Sonraki Adım

Bu konu boyunca sözleşme hep aynı biçimde kuruldu: sunucu hangi kaynağı hangi alanlarla
vereceğine karar verir, istemci verilenle yetinir. Kısmi yanıt ve alan seçimi bu katılığı
gevşetmişti ama sınırı sunucu çiziyordu. Sorgu temelli yaklaşım bu ilişkiyi tersine
çevirir: tip sistemi sunucuda tanımlanır, hangi alanların döneceğine **istemci** karar
verir ve yanıt sorgunun biçimini alır. Sonraki konu bu yaklaşımı ayrıntısıyla kurar; ilk
ders şema ve tip sistemini yazar, nesne, skaler, arayüz ve birleşim tiplerini tanımlar ve
tipleri gerçekten çözümleyen küçük bir çalıştırıcı üzerinde gösterir.
