---
title: 'HTTP Sunucusu'
source: 'https://academia.sh/tr/kurslar/go-uygulamalar/http-sunucusu'
course: 'Standart Kütüphane ve Ağ Uygulamaları'
language: tr
updated: '2026-08-23T16:55:13+00:00'
license: 'CC BY-SA 4.0'
---

# HTTP Sunucusu

Kursun açılış dersi. Bir isteğin taşıdığı yol, üstbilgi ve gövdenin üçünün de tip bilgisi taşımadığı `net/http/httptest` ile gerçek bağlantı noktası açmadan ölçülüyor; ara katman kalıbının bir işleyiciyi saran başka bir işleyici olduğu üç halkalı bir zincirle koşturuluyor. Sınırlayıcı ölçüm: içerik tipi üstbilgisi bir ipucudur, tip değil — yanlış bir ipucu sunucu tarafında hiçbir hata üretmiyor, hata yalnızca gövdeyi o ipucuna göre çözmeye çalışan tarafta ortaya çıkıyor.

Önceki kursun kapanışı bir goroutine'in nasıl duracağını ölçtü ve durmanın çoğunlukla
**başka birinin yaptığı bir işe** bağlı olduğunu gösterdi: bir kanal kapanmadan, bir bağlam
iptal edilmeden, bekleyen goroutine kendi kararını veremiyordu. O ölçüm hep programın
**içindeydi** — iki goroutine aynı ikili çalıştırılabilir dosyanın parçasıydı, aynı tip
sistemine bağlıydı. Bu ders soruyu programın **dışına** taşıyor: bir değer ağa, dosyaya,
şablona ya da veritabanına gittiğinde ne oluyor? Karşı taraf aynı derleyiciden geçmedi, aynı
tip tanımlarını görmedi — bazen aynı dilde bile yazılmadı. Kursun tamamı bu tek soruyu on iki
farklı sınırda soruyor: **bir değer sınırı geçerken tip bilgisine ne oluyor?** Bu ders sınırın
en sık geçileni ile başlıyor: bir HTTP isteği.

Bir sunucunun ne iş yaptığı, kimin isteği ne zaman işlediği ve ara katmanların bir zincirde
nasıl sıralandığı önceki kurslarda kavram olarak kuruldu. Burada tekrarlanmıyor; bu dersin
sorduğu soru farklı — sunucunun **görevi** değil, sunucuya **gelen ve sunucudan çıkan verinin**
tip sistemiyle ilişkisi. Bu ayrım kursun geri kalanında da sürüyor: her ders bir katmanın
görevini değil, o katmanın sınırındaki verinin akıbetini ölçüyor.

## İşleyicinin Aldığı Şey: Yol, Üstbilgi, Gövde

Bir istemci bir HTTP isteği gönderdiğinde, isteğin içinde ne olduğuna dair tek kaynak
kablodan geçen bayt dizisidir. Go'nun standart kitaplığı bu bayt dizisini ayrıştırıp bir
`*http.Request` değerine dönüştürüyor, ama dönüştürülen şey hâlâ üç ham parça: **yol**
(`r.URL.Path`, bir dizgi), **üstbilgiler** (`r.Header`, dizgiden dizgi dilimine bir sözlük)
ve **gövde** (`r.Body`, bayt akıtan bir okuyucu). İsteği gönderen tarafın kafasında yol bir
tamsayı kimlik, üstbilgi bir yapılandırılmış değer, gövde bir kayıt olabilir — ama sınırı
geçtikten sonra elde kalan yalnızca dizgi ve bayttır. Go'nun tip sistemi bu üçünün hiçbirine
"bu aslında bir tamsayıydı" ya da "bu aslında bir yapıydı" demiyor; söylemeye yetkisi de yok,
çünkü kablonun karşı ucunda hangi programın çalıştığını, o programın hangi tipleri
tanımladığını bilmiyor.

Aşağıdaki kaynak bu ölçümün ortak parçalarını kuruyor: sonuçları sınıflandırıp yazdıran bir
yardımcı, isteğin yolunu ve bir üstbilgisini okuyup gövdenin uzunluğunu yanıt olarak yazan bir
çekirdek işleyici, ve bu çekirdeği saran iki ara katman.

```go
// istekcekirdek.go — istek isleyicisinin ve ara katmanin ortak parcalari
package main

import (
	"fmt"
	"io"
	"net/http"
)

var parcaSayisi, kayboluyorSayisi int
var sonOkunanGovde []byte

// olcumSatiri, bu dersin sinif adlarindan birini (korunuyor / yeniden / kayboluyor)
// bir kanitla birlikte yazdirir ve toplami tutar.
func olcumSatiri(parca, sinif, kanit string) {
	parcaSayisi++
	if sinif == "kayboluyor" {
		kayboluyorSayisi++
	}
	fmt.Printf("%-30s %-10s %s\n", parca, sinif, kanit)
}

// cekirdekIsleyici, gelen istegin yolunu, bir ustbilgisini ve govdesini okuyup
// duz metin bir yanit yazar. Yanita sabit bir icerik tipi ipucu ekler; bu ipucunun
// dogru olup olmadigini denetlemek bu islevin isi degildir.
func cekirdekIsleyici(w http.ResponseWriter, r *http.Request) {
	govde, _ := io.ReadAll(r.Body)
	sonOkunanGovde = govde
	kimlik := r.Header.Get("X-Istek-Kimligi")
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	fmt.Fprintf(w, "yol=%s kimlik=%s govde-uzunlugu=%d", r.URL.Path, kimlik, len(govde))
}

// araGunlukleme, sardigi isleyiciyi cagirmadan once bir sayaci artirir; govdeye
// ya da baglantiya dokunmuyor.
func araGunlukleme(sonraki http.Handler, sayac *int) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		*sayac++
		sonraki.ServeHTTP(w, r)
	})
}

// araBasliklandirma, yanita sabit bir ustbilgi ekleyip zinciri surdurur.
func araBasliklandirma(sonraki http.Handler, sayac *int) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		*sayac++
		w.Header().Set("X-Zincir-Adimi", "gecti")
		sonraki.ServeHTTP(w, r)
	})
}
```

Ölçüm bu çekirdeği bir istekle koşturuyor. İstek `net/http/httptest` ile kuruluyor —
`httptest.NewRequest` gerçek bir bağlantı noktası açmadan bellekte bir `*http.Request`
üretiyor, `httptest.NewRecorder` ise gerçek bir bağlantı yerine yanıtı bir arabelleğe
yazan sahte bir `http.ResponseWriter` veriyor. İstemci ile sunucu arasında hiçbir gerçek
soket, adres ya da süre yok.

```go
// istekolcumu.go — bir istegin tasidigi parcalarin tip bilgisi ve ara katman zinciri
package main

import (
	"bytes"
	"fmt"
	"net/http"
	"net/http/httptest"
)

func main() {
	fmt.Println("-- isleyicinin aldigi sey: bayt ve metin --")

	govde := bytes.NewBufferString("adet=3")
	istek := httptest.NewRequest(http.MethodPost, "/urunler/42", govde)
	istek.Header.Set("X-Istek-Kimligi", "1001")

	var gunlukSayaci, basliklandirmaSayaci int
	zincir := araGunlukleme(
		araBasliklandirma(http.HandlerFunc(cekirdekIsleyici), &basliklandirmaSayaci),
		&gunlukSayaci,
	)

	kaydedici := httptest.NewRecorder()
	zincir.ServeHTTP(kaydedici, istek)

	olcumSatiri("istek yolu (r.URL.Path)", "kayboluyor",
		fmt.Sprintf("deger=%q tip=%T", istek.URL.Path, istek.URL.Path))
	olcumSatiri("istek ustbilgisi (Header.Get)", "kayboluyor",
		fmt.Sprintf("deger=%q tip=%T", istek.Header.Get("X-Istek-Kimligi"), istek.Header.Get("X-Istek-Kimligi")))
	olcumSatiri("istek govdesi (okunan bayt)", "kayboluyor",
		fmt.Sprintf("tip=%T uzunluk=%d", sonOkunanGovde, len(sonOkunanGovde)))

	fmt.Println("-- ara katman zinciri --")
	fmt.Println("zincir: araGunlukleme( araBasliklandirma( cekirdekIsleyici ) ) - 3 halka")
	fmt.Printf("gunlukleme cagrisi=%d basliklandirma cagrisi=%d (ikisi de bu dersin yazdigi kod)\n",
		gunlukSayaci, basliklandirmaSayaci)
	fmt.Printf("durum kodu=%d zincir ustbilgisi=%s\n", kaydedici.Code, kaydedici.Header().Get("X-Zincir-Adimi"))
	fmt.Println("yanit govdesi:", kaydedici.Body.String())

	fmt.Printf("bu istegin %d parcasindan tip bilgisi tasiyani=%d (kayboluyor=%d)\n",
		parcaSayisi, parcaSayisi-kayboluyorSayisi, kayboluyorSayisi)
}
```

```
-- isleyicinin aldigi sey: bayt ve metin --
istek yolu (r.URL.Path)        kayboluyor deger="/urunler/42" tip=string
istek ustbilgisi (Header.Get)  kayboluyor deger="1001" tip=string
istek govdesi (okunan bayt)    kayboluyor tip=[]uint8 uzunluk=6
-- ara katman zinciri --
zincir: araGunlukleme( araBasliklandirma( cekirdekIsleyici ) ) - 3 halka
gunlukleme cagrisi=1 basliklandirma cagrisi=1 (ikisi de bu dersin yazdigi kod)
durum kodu=200 zincir ustbilgisi=gecti
yanit govdesi: yol=/urunler/42 kimlik=1001 govde-uzunlugu=6
bu istegin 3 parcasindan tip bilgisi tasiyani=0 (kayboluyor=3)
```

**AG1.** Yolun `"/urunler/42"` olması, `42` rakamının bir tamsayı **anlamına gelmesi**
gerektiğini düşündürüyor — kaynak kodda bu değer bir ürün kimliğiydi. Ama ölçüm `%T` ile
sorduğunda yanıt tektir: `string`. Go'nun yönlendiricisi yolu ayrıştırırken hiçbir rakam
dizisini otomatik olarak sayıya çevirmiyor; `/urunler/42` ile `/urunler/kirk-iki` aynı tipte
iki dizgidir, aralarındaki fark yalnızca içerik. Aynı gözlem üstbilgi için de geçerli: istek
kimliği üstbilgisindeki `"1001"` değeri de bir dizgi, isteyen tarafın kafasında bir sayısal
kimlik olsa bile. Kablonun üzerinden geçen hiçbir şey kendi tipini taşımıyor; taşıyan tek şey
karakterler.

**AG2.** Gövde bu üçünün en açık örneği: `io.ReadAll(r.Body)` her zaman `[]byte` döndürüyor,
gövdenin içeriği ister bir sayı, ister bir yapı, ister rastgele metin olsun. Ölçümde
`uzunluk=6` çıkıyor çünkü `"adet=3"` dizgisi altı bayt — ama bu uzunluk gövdenin **neyi**
temsil ettiğini söylemiyor, yalnızca **kaç bayt** olduğunu söylüyor. Bu üç satırın üçü de
`kayboluyor` sınıfında; bu dersin ölçtüğü isteğin üç parçasından tip bilgisi taşıyanı sıfır.
Kursun on üç sınır geçişinin tam dağılımı (1 korunuyor, 5 yeniden kuruluyor, 7 kayboluyor)
JSON Dizileştirme dersinde kuruluyor; bu ders yalnız kendi diliminin üç satırını basıyor.

## Ara Katman: Bir İşleyiciyi Saran Başka Bir İşleyici

`http.Handler`, Go'nun standart kitaplığında tek bir yöntemle tanımlanan bir arayüz:
`ServeHTTP(http.ResponseWriter, *http.Request)`. Bu küçüklük bilinçli bir tasarım kararı —
bir işleyici, bu tek yöntemi gerçekleştiren **her şey** olabiliyor, dolayısıyla bir işleyiciyi
başka bir işleyicinin içine gömmek özel bir çerçeve gerektirmiyor, sıradan bir fonksiyon
çağrısı yeterli. `araGunlukleme` ve `araBasliklandirma` işlevlerinin ikisi de aynı kalıbı
izliyor: bir `http.Handler` alıyor, kendi mantığını çalıştıran yeni bir `http.HandlerFunc`
döndürüyor, ve döndürdüğü işleyicinin gövdesinde aldığı işleyicinin `ServeHTTP` yöntemini
çağırıyor. Sonuç bir **zincir**: dıştaki halka önce çalışıyor, işini yapıp içteki halkayı
çağırıyor, o da kendi işini yapıp bir sonrakini çağırıyor — en içte çekirdek işleyici durana
kadar.

Bu zincir kaynak kodda `main` işlevindeki tek bir ifadeyle, `araGunlukleme` çağrısının
içine `araBasliklandirma` çağrısının, onun içine de çekirdek işleyicinin konulmasıyla elle
kuruluyor: üç halka, üçü de bu dersin yazdığı kod, hiçbiri bir kayıt listesinden ya da bir
etiketten otomatik olarak
keşfedilmiyor. Çıktıdaki `gunlukleme cagrisi=1` ve `basliklandirma cagrisi=1` satırları bunu
doğrudan sayıyor: zincirdeki iki ara katmanın ikisi de tam olarak bir kez çağrıldı, çünkü
istek de tam olarak bir kez geldi. Sunucunun görevi, istek işleme zincirinin genel yapısı ve
ara katmanın neden var olduğu önceki kurslarda kavram olarak kuruldu; burada ölçülen şey o
kavramın Go'nun standart kitaplığındaki somut karşılığı — bir işleyicinin başka bir işleyiciyi
sarması dışında hiçbir gizli düzenek yok.

**AG3.** Zincirin sırası sonucu etkiliyor. `araBasliklandirma`, `X-Zincir-Adimi` üstbilgisini
kendi `ServeHTTP` gövdesinde, iç işleyiciyi çağırmadan **önce** ayarlıyor; ama bu üstbilgi
yanıta yalnızca `w.WriteHeader` çağrıldığı anda kilitleniyor, ve `WriteHeader`i çağıran
`cekirdekIsleyici`. Yani `araBasliklandirma`nın eklediği üstbilgi, çekirdek henüz yanıtı
yazmadan önce ayarlandığı için başarıyla yanıta giriyor — çıktıdaki
`zincir ustbilgisi=gecti` satırı bunu doğruluyor. Sıra tersine çevrilseydi, yani
`araBasliklandirma`
çekirdekten **sonra** çalışsaydı, `WriteHeader` zaten çağrılmış olacağı için üstbilgi
sessizce yok sayılırdı — Go bu durumda hata vermiyor, yalnızca üstbilgiyi görmezden geliyor.

**AG4.** İki ara katmanın da imzası aynı: bir `http.Handler` ve bir sayaç işaretçisi alıp
yeni bir `http.Handler` döndürüyorlar. Bu tekdüzelik, üçüncü bir ara katmanın aynı zincire
eklenmesini tek satırlık bir sarmalamaya indiriyor — zincire yeni bir halka eklemek, listeye
yeni bir eleman eklemekten farklı bir işlem değil, sadece bir fonksiyon çağrısını bir
öncekinin içine koymak. Ama bu kolaylığın bir bedeli var: zincir ne kadar uzarsa, bir isteğin
`cekirdekIsleyici`ye ulaşana kadar kaç halkadan geçtiğini görmek o kadar çok, iç içe geçmiş
fonksiyon çağrısını okumayı gerektiriyor. Zincirin kendisi hiçbir yerde bir liste olarak
durmuyor — yalnızca `main` işlevindeki tek bir ifade.

Bu, zincirin **tip bilgisiyle** ilişkisini de belirliyor: `araGunlukleme` ve
`araBasliklandirma` işlevlerinin ikisi de kendi işlerini görmek için isteğin ya da yanıtın
içeriğine hiç bakmıyor, yalnızca `*http.Request` ve `http.ResponseWriter` değerlerini
olduğu gibi bir sonraki halkaya aktarıyor. Bir ara katman isteğin gövdesindeki bir alanı
okumak isteseydi, bu ders boyunca görülen aynı sınırla karşılaşırdı — gövde yine yalnızca
bayt, ara katman da yalnızca kendi yazdığı ayrıştırma koduyla o baytlara bir anlam
yükleyebilirdi.

## Sınırlayıcı Ölçüm: İçerik Tipi Bir İpucudur, Tip Değil

Çekirdek işleyicinin yanıtına eklediği `Content-Type: application/json` üstbilgisi, ilk
bakışta yanıt gövdesinin tipini garanti ediyormuş gibi görünüyor. Değil. Bu üstbilgi bir
dizgiden başka bir şey değil ve onu kim okursa okusun, gövdenin gerçekten o biçimde olup
olmadığını denetleyen hiçbir mekanizma araya girmiyor. Aşağıdaki ölçüm bunu bilerek kırıyor:
işleyici `application/json` ipucu veriyor ama gövdeye düz metin yazıyor. Bu, önceki ölçümdeki
üç `kayboluyor` satırından farklı bir gözlem: oradaki satırlar isteğin taşıdığı bilginin
**hiç var olmadığını** ölçtü, buradaki ölçüm ise var olan bir bilginin (üstbilginin kendisi)
**yanlış olabileceğini** ve bunun hiçbir yerde yakalanmadığını gösteriyor.

```go
// yanlisipucu.go — icerik tipi bir ipucudur, hatasi kosarken degil cozerken cikar
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
)

// yalanciIsleyici, govdeye duz metin yazip ustbilgide "application/json" ipucu verir.
// Ipucu ile govde arasindaki uyumu denetleyen hicbir kod yoktur.
func yalanciIsleyici(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	fmt.Fprint(w, "bu govde json degil, duz metin")
}

func main() {
	fmt.Println("-- icerik tipi bir ipucudur, tip degil --")

	istek := httptest.NewRequest(http.MethodGet, "/rapor", nil)
	kaydedici := httptest.NewRecorder()
	http.HandlerFunc(yalanciIsleyici).ServeHTTP(kaydedici, istek)

	fmt.Printf("durum kodu=%d (ipucu yanlisken bile basarili)\n", kaydedici.Code)
	fmt.Printf("ustbilgideki ipucu=%s\n", kaydedici.Header().Get("Content-Type"))

	var hedef map[string]any
	cozHatasi := json.Unmarshal(kaydedici.Body.Bytes(), &hedef)
	fmt.Printf("govdeyi json olarak cozme hatasi var mi=%t\n", cozHatasi != nil)
	fmt.Println("hata sunucuda degil, govdeyi ipucuna gore cozmeye calisan tarafta cikiyor")
}
```

```
-- icerik tipi bir ipucudur, tip degil --
durum kodu=200 (ipucu yanlisken bile basarili)
ustbilgideki ipucu=application/json
govdeyi json olarak cozme hatasi var mi=true
hata sunucuda degil, govdeyi ipucuna gore cozmeye calisan tarafta cikiyor
```

**AG5.** Durum kodu `200` — yani işleyici, gövdenin ipucuyla uyuşmadığını hiç fark etmeden
başarıyla tamamlandı. `http.ResponseWriter` arayüzünde `Content-Type` üstbilgisini gövdeyle
karşılaştıran, ya da gövdenin gerçekten geçerli JSON olup olmadığını denetleyen hiçbir
adım yok — üstbilgi, alıcıya "bu gövdeyi böyle yorumlamayı dene" diyen bir öneri, alıcının
uyması gereken bir kural değil. Hata ancak bir taraf bu öneriye güvenip `json.Unmarshal`
çağırdığında ortaya çıkıyor; ölçümde bu çağrı `main` işlevinin kendisinde yapılıyor ve
`cozHatasi != nil` sonucu `true` çıkıyor. Sunucu tarafında hiçbir uyarı, hiçbir günlük satırı,
hiçbir hata dönüşü yok — sınır geçişinin kendisi tamamen sessiz.

**AG6.** Bu gözlem, dersin baştaki iddiasını netleştiriyor: sınırın ötesine geçen bir yanıtın
yanında taşıdığı **tek** ipucu böyle bir üstbilgidir, ve bu ipucu bir tip değil, bir dizgidir
— aynı `r.URL.Path` ya da `r.Header.Get` gibi, `%T` ile sorulduğunda yine `string` çıkıyordu.
İçerik tipi üstbilgisinin doğru olması, sunucuyu yazan tarafın disiplinine kalmış bir şey;
Go'nun derleyicisi ya da çalışma zamanı bu disiplini zorlamıyor. Bu sınırlayıcı ölçüm,
gövdenin kendisinin taşıdığı bilgiyle üstbilgide yazan bilginin **birbirinden bağımsız** iki
şey olduğunu, ikisinin birbirini doğrulamadığını gösteriyor.

Standart kitaplığın kendisi bu ikisini birbirine bağlayan hiçbir denetim eklemiyor; ekleyecek
olan, işleyiciyi yazan taraf. `cekirdekIsleyici` isteseydi, `w.Write` çağrısından önce
gövdenin gerçekten geçerli JSON olduğunu kendi kodunda doğrulayabilirdi — ama bu doğrulama
dilin bir parçası değil, yazarın bir kararı. Sınırın bu tarafında kalan tek garanti, üstbilgi
ile gövde arasındaki tutarlılığı **kimsenin otomatik olarak sağlamadığıdır**; bu tutarlılığı
kuran her katman, kendi kodunda bunu açıkça yapmak zorunda.

## Özet

- Bir HTTP isteğinin yolu, üstbilgileri ve gövdesi Go'nun standart kitaplığında sırasıyla
  dizgi, dizgi ve bayt dilimi olarak temsil ediliyor; üçü de `%T` ile sorgulandığında hiçbir
  özel tip taşımadığı görülüyor.
- Ölçüm `net/http/httptest` ile gerçek bir bağlantı noktası açmadan kuruluyor:
  `httptest.NewRequest` bellekte bir istek üretiyor, `httptest.NewRecorder` yanıtı bir
  arabelleğe yazan sahte bir yanıt yazıcı sağlıyor.
- Ara katman kalıbı, `http.Handler` arayüzünün tek yöntemli olması sayesinde bir işleyiciyi
  başka bir işleyicinin içine sarmaktan ibaret; zincirdeki her halka çağıranın yazdığı sıradan
  bir fonksiyon çağrısı, otomatik keşif ya da kayıt yok.
- Ara katmanların çalışma sırası sonucu etkiliyor: bir üstbilgi ancak `WriteHeader`
  çağrılmadan önce eklenirse yanıta giriyor, sıra tersine dönerse üstbilgi hatasızca yok
  sayılıyor.
- İçerik tipi üstbilgisi bir ipucu, bir garanti değil: yanlış bir ipucu sunucu tarafında
  hiçbir hata üretmiyor, hata yalnızca gövdeyi o ipucuna güvenerek çözmeye çalışan tarafta
  ortaya çıkıyor.
- Kursun on üç sınır geçişinin tam dağılımı bu derste kurulmuyor; bu ders yalnız isteğin
  sunucu tarafındaki üç parçasını ölçüyor, tam tabloyu JSON Dizileştirme dersi kuruyor.

## Sonraki Adım

Bu ders, sınırı **alıcı** tarafından ölçtü: bir isteğin sunucuya ne getirdiğini, ve sunucunun
gövdeye ne kadar körü körüne güvendiğini. Sıradaki ders aynı sınırı **gönderen** taraftan
soruyor: bir istemci bir isteği gönderirken neyi garanti etmek zorunda, ve bir yanıtı aldıktan
sonra hangi sorumlulukları devralıyor. Bir yanıt gövdesi de bu dersteki istek gövdesi gibi
bir bayt akışı, ama bu kez akışı kimin ne zaman kapatacağı, ve zaman aşımının bir süre yerine
neyle kurulduğu ölçülüyor.
