Skip to content

Bölüm 1 / 11

HTTP protokolü

İstek/yanıt, metotlar, durum kodları, header'lar, önbellek.

12 dakikalık okuma

5. hatta bir web servisi kurdun: uç noktalar yazdın, durum kodları döndürdün, Location başlığı koydun. Ama bunların neden öyle olduğunu konuşmadık — 8. derste "ayrıntısı 6. hatta" diyerek geçtik. Bu ders o borcu ödüyor ve hattın temelini atıyor: HTTP, tüm web API'lerinin ortak dilidir. Bu dili doğru konuşamayan bir servis çalışır ama tarayıcılar, vekiller, önbellekler ve istemci kütüphaneleri onunla anlaşamaz.

01Ne öğreneceksin

  • Bir HTTP mesajının ham hâli: istek satırı, başlıklar, gövde
  • Durumsuzluk ne demek ve neyi mümkün kılar
  • Güvenli ve idempotent metotlar — yeniden denemenin temeli
  • Durum kodu sınıfları ve sık karıştırılan çiftler (401/403, 400/422, 502/503/504)
  • İçerik pazarlığı: Accept, Content-Type, 406 ve 415
  • Önbellek: Cache-Control, ETag, 304
  • Koşullu güncelleme ve kayıp güncelleme sorunu (If-Match, 412)
  • HTTP/1.1, HTTP/2 ve HTTP/3 arasındaki gerçek farklar

02Mesajın ham hâli

HTTP, metin tabanlı bir istek–yanıt protokolüdür. İstemci bir mesaj gönderir, sunucu bir mesaj döndürür. Süslü hiçbir şey yoktur; tarayıcının, curl'ün ve senin HttpClient'ının gönderdiği şey aynıdır:

GET /api/siparisler/42?ayrinti=tam HTTP/1.1     ← istek satırı: metot, hedef, sürüm
Host: siparis.ornek.local                       ← başlıklar
Accept: application/json
Authorization: Bearer eyJhbGciOi...
User-Agent: curl/8.5.0
                                                ← boş satır: başlıklar bitti
                                                ← gövde (GET'te yok)
HTTP/1.1 200 OK                                 ← durum satırı: sürüm, kod, sebep
Content-Type: application/json; charset=utf-8
Content-Length: 87
ETag: "v7"
Cache-Control: private, max-age=60

{"no":42,"urun":"kitap","adet":2,"durum":"Onaylandi","tutar":{"deger":249.9,"birim":"TRY"}}

Üç yapısal kural: (1) Başlıklar Ad: değer biçimindedir ve adları büyük/küçük harfe duyarsızdır. (2) Başlıklarla gövdeyi boş bir satır ayırır. (3) Gövdenin nerede bittiği ya Content-Length ile ya da parçalı aktarımla (Transfer-Encoding: chunked) bildirilir — akış hâlinde üretilen yanıtlarda uzunluk baştan bilinemediği için ikincisi kullanılır.

Durumsuzluk (stateless) protokolün en önemli özelliğidir: sunucu, iki istek arasında istemciye ait hiçbir şey hatırlamak zorunda değildir. Her istek, kendisini anlamlı kılan her şeyi (kimlik, hedef kaynak, istenen biçim) yanında taşır. 5. hattın 13. dersinde ölçeklemenin ön şartı olarak gördüğün durumsuzluk kuralı buradan gelir: istek kendine yeterliyse hangi kopyaya düştüğünün önemi kalmaz.

Bir ayrım şimdiden yapılmalı: URL bir kaynağı adresler. https://siparis.ornek.local/api/siparisler/42?ayrinti=tam adresinde şema (https), sunucu adı, yol (/api/siparisler/42) ve sorgu dizesi (ayrinti=tam) vardır. Yol hangi kaynak olduğunu, sorgu o kaynağın nasıl istendiğini söyler. Bu ayrım 3 ve 4. derslerin temelidir.

03Metotlar: güvenli ve idempotent

Metot, kaynak üzerinde ne yapılmak istendiğini söyler. İki özellik bütün protokolü şekillendirir:

  • Güvenli (safe): sunucuda gözle görülür bir değişiklik yaratmaz. Okuma amaçlıdır; vekiller ve tarayıcılar bu isteği serbestçe önbellekleyebilir, önden çekebilir.
  • İdempotent: aynı istek bir kez de gönderilse on kez de gönderilse sonuç durumu aynı olur. Dikkat: yanıt aynı olmak zorunda değildir — ikinci DELETE 404 dönebilir; önemli olan sunucunun durumudur.
MetotNe yaparGüvenliİdempotentGövde
GETKaynağın temsilini getirirEvetEvetYok
HEADGET'in gövdesiz hâli (başlıklar)EvetEvetYok
OPTIONSDesteklenen yetenekleri sorarEvetEvetYok
POSTKaynağa veri gönderir; genelde yeni kayıt yaratırHayırHayırVar
PUTKaynağı verilen temsille tümüyle değiştirirHayırEvetVar
PATCHKaynağı kısmen güncellerHayırDuruma göreVar
DELETEKaynağı silerHayırEvetNadiren

Bu tablo, 5. hattaki yeniden deneme kuralının kaynağıdır. 11. derste "yalnız idempotent işlem yeniden denenir" demiştik: ağ hatası aldığında isteğin sunucuya ulaşıp ulaşmadığını bilemezsin. GET ya da PUT ise tekrar göndermek zararsızdır; POST ise ilk istek başarılı olup yanıtı kaybolmuş olabilir ve tekrar göndermek ikinci bir kayıt yaratır. Çözüm yine oradaydı: idempotentlik anahtarı, yani POST'a yapay olarak idempotentlik kazandırmak.

PUT ile POST farkı sık sorulur: PUT hedef adresi istemci belirler ve gönderilen temsil kaynağın tamamıdır — bu yüzden idempotenttir, aynı içeriği on kez göndersen sonuç aynıdır. POST'ta hedef bir koleksiyondur, yeni kaynağın adresini sunucu belirler ve yanıtın Location başlığıyla bildirir. 5. hattın 8. dersinde CreatedAtRoute ile ürettiğin 201 yanıtının gerekçesi budur.

PATCH'in idempotentliği gövdeye bağlıdır: "durumu 'iptal' yap" idempotent, "stoğu 1 azalt" değildir.

04Durum kodları

Üç haneli kodun ilk hanesi sınıfı belirler ve istemci kütüphaneleri kararlarını önce buna göre verir: 1xx bilgi, 2xx başarı, 3xx yönlendirme, 4xx istemci hatası, 5xx sunucu hatası.

KodAnlamıNe zaman
200 OKBaşarılı, gövde varOkuma, güncelleme
201 CreatedKaynak yaratıldıPOST; Location zorunlu sayılmalı
202 AcceptedKabul edildi, henüz yapılmadıKuyruğa alınan uzun iş (5. hat, 12. ders)
204 No ContentBaşarılı, gövde yokDELETE, gövdesiz güncelleme
304 Not ModifiedDeğişmedi, önbelleğindekini kullanKoşullu GET
400 Bad Requestİstek anlaşılamadıBozuk JSON, yanlış tip
401 UnauthorizedKimliğin yok ya da geçersizJeton yok/süresi dolmuş
403 ForbiddenKimliğin var ama yetkin yokRol yetersiz
404 Not FoundKaynak yokYanlış adres ya da silinmiş kayıt
405 Method Not AllowedKaynak var, metot desteklenmiyorAllow başlığı eklenir
409 ConflictKaynağın mevcut durumuyla çelişiyorÇift kayıt, geçersiz durum geçişi
410 GoneVardı, kalıcı olarak kaldırıldıKullanımdan kaldırılmış kaynak
412 Precondition FailedKoşul sağlanmadıIf-Match uyuşmadı
415 Unsupported Media TypeGövde biçimi desteklenmiyorXML gönderildi, JSON bekleniyor
422 Unprocessable ContentBiçim doğru, anlam geçersizİş kuralına takılan doğrulama
429 Too Many RequestsHız sınırı aşıldıRetry-After eklenir
500 Internal Server ErrorBeklenmeyen sunucu hatasıYakalanmamış istisna
502 / 503 / 504Vekil hatası / hizmet yok / üst akış zaman aşımıAltyapı ve bağımlılık sorunları

Sık karıştırılan üç çift, mülakat sorusudur:

  • 401 mi 403 mü? "Kim olduğunu bilmiyorum" → 401. "Kim olduğunu biliyorum ama buna iznin yok" → 403. Bir ayrıntı: bazı sistemler kaynağın varlığını gizlemek için 403 yerine bilinçli olarak 404 döner — güvenlik gerekçesiyle yapılan bir tercihtir.
  • 400 mü 422 mi? Gövde ayrıştırılamıyorsa 400; gövde geçerli ama anlamı kabul edilemiyorsa 422. 5. hattın 9. dersinde açık bıraktığımız tartışma buydu; ASP.NET Core'un varsayılanı 400'dür ve tutarlılık doğruluktan önemlidir. Ayrıntı 5. derste.
  • 502 mi 503 mü 504 mü? 502 vekil, üst akıştan bozuk yanıt aldı; 503 hizmet şu an yok (bakım, aşırı yük — Retry-After ile birlikte kullanılır); 504 vekil, üst akışı beklerken zaman aşımına uğradı. Bu üçünü ayırmak, üretim olaylarında sorunun sende mi bağımlılıkta mı olduğunu söyler.

Bir kural: hata, durum koduyla bildirilir. 200 dönüp gövdeye {"basarili": false} yazmak, protokolün kendisini devre dışı bırakır — önbellekler yanıtı başarılı sanar, istemci kütüphaneleri hatayı fark etmez, izleme araçları hata oranını sıfır gösterir. 5. derste bunun doğru biçimini kuracağız.

05Başlıklar, içerik pazarlığı ve sürümler

Başlıklar mesajın üst verisidir. En çok kullanılan gruplar:

BaşlıkYönİşi
Acceptİstekİstenen biçim(ler); karşılanamazsa 406
Content-TypeİkisiGövdenin biçimi; desteklenmiyorsa 415
Accept-Encoding / Content-EncodingİkisiSıkıştırma (gzip, br)
Accept-LanguageİstekDil tercihi
AuthorizationİstekKimlik bilgisi (Bearer, 13. hat)
LocationYanıt201'de yeni kaynağın adresi; 3xx'te hedef
Retry-AfterYanıt429/503'te ne zaman tekrar denenmeli
AllowYanıt405'te desteklenen metotlar

İçerik pazarlığı (content negotiation), aynı kaynağın birden çok temsilde sunulabilmesidir: istemci Accept: application/xml derse sunucu XML, JSON derse JSON döndürür. Kalite değeriyle sıralama yapılabilir: Accept: application/json;q=1.0, application/xml;q=0.8. Sunucu hiçbirini karşılayamıyorsa 406 döner. Bu mekanizma 2. dersin (format seçimi) ve 6. dersin (Accept ile sürümleme) temelidir.

Özel başlıklar için eski X- öneki uzun süredir önerilmiyor; kendi başlıklarını doğrudan anlamlı bir adla (Kiraci-Kodu) yazmak kabul edilen biçimdir — ama pratikte X- önekli başlıklar hâlâ çok yaygındır ve 5. hattaki X-Forwarded-For bunun en bilinen örneğidir.

Sürümler. Protokolün üç sürümü üretimde birlikte yaşar ve farkları API tasarımını değil, performans karakteristiğini değiştirir:

  • HTTP/1.1 — metin tabanlı, bağlantı yeniden kullanılır (keep-alive), ama bir bağlantıda istekler sıraya girer: baştaki yavaş istek arkadakileri bekletir (head-of-line blocking). Tarayıcılar bunu sunucu başına birkaç paralel bağlantı açarak aşar.
  • HTTP/2 — ikili biçim, tek bağlantı üzerinde çoklama (multiplexing): paralel istekler birbirini beklemez; başlıklar sıkıştırılır (HPACK). gRPC'nin ön şartıdır. TCP düzeyinde paket kaybı yine hepsini etkiler.
  • HTTP/3 — TCP yerine UDP üzerinde QUIC; TCP'nin baş bloklama sorunu ortadan kalkar, bağlantı kurulumu daha hızlıdır, ağ değişiminde (Wi-Fi'den mobile) bağlantı korunur.

Sürüm seçimi çoğu kurulumda uygulamada değil, önündeki ters vekilde yapılır — 5. hattın 13. dersinde bunu söylemiştik. API tasarımı açısından üçü de aynı semantiği taşır: metotlar, kodlar ve başlıklar değişmez.

06Önbellek ve koşullu istekler

HTTP'nin en az kullanılan ama en çok kazandıran özelliği önbellektir. Doğru başlıklarla, sunucuna hiç ulaşmayan ya da gövdesiz dönen istekler elde edersin.

Cache-Control: public, max-age=3600          → 1 saat boyunca herkes önbellekleyebilir
Cache-Control: private, max-age=60           → yalnız istemci; ara vekiller saklamasın
Cache-Control: no-cache                      → sakla ama kullanmadan önce doğrula
Cache-Control: no-store                      → hiç saklama (kişisel/hassas veri)
Vary: Accept, Accept-Language                → önbellek anahtarına bu başlıklar da girsin

no-cache ile no-store farkı sık karıştırılır ve güvenlik açısından önemlidir: birincisi "saklayabilirsin ama her seferinde bana sor", ikincisi "hiç saklama". Kişisel veri döndüren uçlarda no-store gerekir; no-cache yeterli sanılırsa veri diske yazılmış olabilir.

Doğrulayıcılar (validators) gövdeyi tekrar göndermemeyi sağlar. Sunucu yanıtına bir ETag (içeriğin sürüm damgası) ya da Last-Modified koyar; istemci bir sonraki isteğinde bunu geri gönderir:

// 1) İlk istek
GET /api/siparisler/42
200 OK
ETag: "v7"

// 2) İkinci istek — koşullu
GET /api/siparisler/42
If-None-Match: "v7"
304 Not Modified          ← gövde YOK; istemci elindekini kullanır

Aynı mekanizmanın yazma tarafı, dağıtık sistemlerin klasik problemini çözer: kayıp güncelleme. İki kullanıcı aynı kaydı okur, ikisi de değiştirir; ikincisinin yazması birincisinin değişikliğini sessizce siler. Koşullu güncelleme bunu engeller:

PUT /api/siparisler/42
If-Match: "v7"                ← "yalnız hâlâ v7 ise yaz"
...
412 Precondition Failed       ← kayıt bu arada değişmiş; istemci yeniden okur

Buna iyimser eşzamanlılık denetimi denir: kilit tutmazsın, çakışmayı yazma anında yakalarsın. ETag değeri genellikle kaydın sürüm sütunundan üretilir — 7. hatta EF Core'un RowVersion alanıyla bunu birebir kuracaksın.

Son bir üretim notu: önbellek başlıkları ölçülerek konur. max-age fazla uzun verilirse kullanıcı eski veriyi görür ve bunu geri almanın yolu yoktur — önbellek zaten dağıtılmıştır. Bu yüzden sık değişen kaynaklarda kısa süre + ETag birlikte kullanılır: süre dolduğunda istemci gövdeyi değil, yalnız doğrulama isteğini gönderir.

07Mini alıştırma — Protokolü elle konuş

Yaklaşık 20 dakika · curl + 5. hatta yazdığın servis

  1. Ham mesaj: curl -v https://ornek.com çalıştır. > ile başlayan satırlar isteğin, < ile başlayanlar yanıtın. İstek satırını ve üç başlığı defterine yaz.
  2. Yalnız başlıklar: aynı adrese curl -I (HEAD) gönder. Gövde geldi mi? Content-Length ne diyor?
  3. Metot semantiği: kendi servisinde bir kaydı iki kez PUT ile güncelle, sonra iki kez POST ile oluştur. Veritabanında kaç kayıt var? Hangisi idempotent çıktı?
  4. 405 üret: yalnız GET destekleyen bir uca DELETE gönder. Dönen kod ne? Allow başlığı geldi mi?
  5. İçerik pazarlığı: Accept: application/xml ile istek yap. Servisin ne döndü — 200 mü 406 mı? Sonra Content-Type: text/plain ile POST gönder ve 415 gördüğünü doğrula.
  6. 401 ve 403: jetonsuz bir istek at, sonra geçerli ama yetkisiz bir jetonla at. İki kod farklı mı? Farklı değilse hangisini düzeltmen gerekir?
  7. Önbellek: bir GET ucuna ETag ekle. İkinci isteği -H 'If-None-Match: "v7"' ile gönder ve 304 aldığını, gövdenin gelmediğini doğrula.
  8. Kayıp güncelleme: aynı kaydı iki terminalde oku, ikisinde de değiştir ve sırayla PUT et. İkinci yazma birincisini ezdi mi? If-Match ekleyip 412 aldığını göster.

08Kendini sına

Güvenli ve idempotent metot ne demek? Hangi metotlar hangisidir?

Güvenli metot sunucuda gözle görülür bir değişiklik yaratmaz; okuma amaçlıdır ve önbelleklenebilir: GET, HEAD, OPTIONS. İdempotent metot bir kez de gönderilse çok kez de gönderilse sunucunun durumunu aynı bırakır — yanıtın aynı olması gerekmez, ikinci DELETE 404 dönebilir. İdempotent olanlar: GET, HEAD, OPTIONS, PUT, DELETE. POST idempotent değildir; PATCH'in idempotentliği gövdeye bağlıdır ("durumu iptal yap" idempotent, "stoğu 1 azalt" değil). Bu ayrım pratikte yeniden denemenin kuralıdır: ağ hatasında isteğin ulaşıp ulaşmadığı bilinemez, bu yüzden yalnız idempotent istek körlemesine tekrarlanabilir; POST ancak idempotentlik anahtarıyla güvenli hâle gelir. PUT ile POST farkı da buradan çıkar: PUT'ta hedef adresi istemci belirler ve gönderilen temsil kaynağın tamamıdır; POST'ta hedef bir koleksiyondur, yeni adresi sunucu belirler ve Location başlığıyla bildirir.

Durum kodu sınıflarını ve sık karıştırılan kodları anlat.

İlk hane sınıfı belirler: 1xx bilgi, 2xx başarı, 3xx yönlendirme, 4xx istemci hatası, 5xx sunucu hatası. Sık karıştırılanlar: 401 kimlik yok ya da geçersiz, 403 kimlik var ama yetki yok (bazı sistemler kaynağın varlığını gizlemek için bilinçli olarak 404 döner). 400 gövde ayrıştırılamadı, 422 gövde geçerli ama anlamı kabul edilemez — ASP.NET Core varsayılanı 400'dür ve API genelinde tutarlılık doğruluktan önemlidir. 404 kaynak yok, 405 kaynak var ama metot desteklenmiyor (Allow başlığı eklenir), 410 vardı ve kalıcı olarak kaldırıldı. 409 kaynağın mevcut durumuyla çelişki, 412 koşul sağlanmadı, 415 gövde biçimi desteklenmiyor, 429 hız sınırı (Retry-After ile). Sunucu tarafında 502 vekil üst akıştan bozuk yanıt aldı, 503 hizmet şu an yok, 504 üst akış beklenirken zaman aşımı. Temel kural: hata durum koduyla bildirilir; 200 dönüp gövdeye "başarısız" yazmak önbellekleri, istemci kütüphanelerini ve izleme araçlarını yanıltır.

İçerik pazarlığı nedir, 406 ve 415 ne zaman döner?

İçerik pazarlığı, aynı kaynağın birden çok temsilde sunulabilmesidir. İstemci Accept başlığıyla istediği biçimi bildirir (kalite değerleriyle sıralayabilir: application/json;q=1.0, application/xml;q=0.8), sunucu bunlardan birini seçip yanıtın Content-Type'ında hangisini döndürdüğünü söyler. Sunucu istenen biçimlerden hiçbirini üretemiyorsa 406 Not Acceptable döner. Ters yön isteğin gövdesidir: istemci gövdenin biçimini Content-Type ile bildirir; sunucu bu biçimi okuyamıyorsa 415 Unsupported Media Type döner. Yani 406 yanıt biçimiyle, 415 istek gövdesinin biçimiyle ilgilidir. Aynı mekanizma dil (Accept-Language) ve sıkıştırma (Accept-Encoding) için de çalışır ve önbelleğin doğru anahtarlanması için sunucu Vary başlığıyla hangi başlıkların temsili değiştirdiğini bildirmelidir.

ETag ne işe yarar? Koşullu istekleri iki yönde de anlat.

ETag, bir kaynağın o anki temsilinin sürüm damgasıdır. Okuma tarafında bant genişliği kazandırır: sunucu yanıta ETag: "v7" koyar, istemci bir sonraki isteğinde If-None-Match: "v7" gönderir; kaynak değişmediyse sunucu gövdesiz 304 Not Modified döner ve istemci elindekini kullanır. Yazma tarafında kayıp güncellemeyi engeller: iki kullanıcı aynı kaydı okuyup değiştirdiğinde ikincisinin yazması birincisininkini sessizce silebilir. İstemci If-Match: "v7" ile "yalnız hâlâ v7 ise yaz" der; kayıt bu arada değiştiyse sunucu 412 Precondition Failed döner ve istemci yeniden okuyup birleştirir. Buna iyimser eşzamanlılık denetimi denir — kilit tutulmaz, çakışma yazma anında yakalanır; ETag değeri genellikle kaydın sürüm sütunundan üretilir. Önbellek tarafında ayrıca Cache-Control vardır ve no-cache ("sakla ama doğrula") ile no-store ("hiç saklama") farkı güvenlik açısından önemlidir; kişisel veri dönen uçlarda no-store gerekir.

09Sınav köşesi

Yazılıda nasıl sorulur

(1) HTTP isteğinin ve yanıtının bölümlerini yazınız. (2) HTTP metotlarını yazıp güvenli/idempotent olanları işaretleyiniz. (3) PUT ile POST farkı — klasik soru. (4) PUT ile PATCH farkı. (5) Durum kodu sınıfları ve verilen senaryolara uygun kodu seçme. (6) 401 ile 403, 400 ile 422, 502 ile 503 ile 504 farkı. (7) 201 yanıtında hangi başlık bulunur. (8) İçerik pazarlığı nedir; 406 ve 415 ne zaman döner. (9) Cache-Control direktifleri; no-cache ile no-store farkı. (10) ETag ve koşullu istekler; iyimser eşzamanlılık. (11) HTTP/1.1, HTTP/2 ve HTTP/3 farkları. (12) Durumsuzluk nedir, ne kazandırır.

Sözlüde nasıl anlatılır — 2 dakikalık taslak

Düzen: (1) Model — "HTTP, durumsuz bir istek–yanıt protokolüdür; her istek kendini anlamlı kılan her şeyi yanında taşır, bu yüzden hangi sunucu kopyasına düştüğü fark etmez." (2) Metot — "metot niyeti söyler; ikisi kritik: güvenli olan sunucuyu değiştirmez, idempotent olan tekrarlandığında durumu değiştirmez. GET, PUT, DELETE idempotenttir, POST değildir." (3) Kod — "sonucu durum koduyla bildiririm: yaratmada 201 ve Location, gövdesiz başarıda 204; 401 kimlik yok, 403 yetki yok, 409 durum çelişkisi, 429 hız sınırı." (4) Başlık — "biçimi içerik pazarlığıyla seçerim, karşılayamazsam 406, gövdeyi okuyamazsam 415 dönerim." (5) Kıdem vuruşu — "önbelleği protokolün parçası olarak kullanırım: kısa max-age ve ETag ile istemci gövdeyi değil doğrulama isteğini gönderir, 304 alır. Aynı ETag'i yazma tarafında If-Match ile kullanıp kayıp güncellemeyi 412 ile engellerim. Ve hatayı asla 200 içinde göndermem — önbellek, istemci kütüphanesi ve izleme aracı durum koduna bakar."