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
DELETE404 dönebilir; önemli olan sunucunun durumudur.
| Metot | Ne yapar | Güvenli | İdempotent | Gövde |
|---|---|---|---|---|
GET | Kaynağın temsilini getirir | Evet | Evet | Yok |
HEAD | GET'in gövdesiz hâli (başlıklar) | Evet | Evet | Yok |
OPTIONS | Desteklenen yetenekleri sorar | Evet | Evet | Yok |
POST | Kaynağa veri gönderir; genelde yeni kayıt yaratır | Hayır | Hayır | Var |
PUT | Kaynağı verilen temsille tümüyle değiştirir | Hayır | Evet | Var |
PATCH | Kaynağı kısmen günceller | Hayır | Duruma göre | Var |
DELETE | Kaynağı siler | Hayır | Evet | Nadiren |
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ı.
| Kod | Anlamı | Ne zaman |
|---|---|---|
| 200 OK | Başarılı, gövde var | Okuma, güncelleme |
| 201 Created | Kaynak yaratıldı | POST; Location zorunlu sayılmalı |
| 202 Accepted | Kabul edildi, henüz yapılmadı | Kuyruğa alınan uzun iş (5. hat, 12. ders) |
| 204 No Content | Başarılı, gövde yok | DELETE, gövdesiz güncelleme |
| 304 Not Modified | Değişmedi, önbelleğindekini kullan | Koşullu GET |
| 400 Bad Request | İstek anlaşılamadı | Bozuk JSON, yanlış tip |
| 401 Unauthorized | Kimliğin yok ya da geçersiz | Jeton yok/süresi dolmuş |
| 403 Forbidden | Kimliğin var ama yetkin yok | Rol yetersiz |
| 404 Not Found | Kaynak yok | Yanlış adres ya da silinmiş kayıt |
| 405 Method Not Allowed | Kaynak var, metot desteklenmiyor | Allow başlığı eklenir |
| 409 Conflict | Kaynağın mevcut durumuyla çelişiyor | Çift kayıt, geçersiz durum geçişi |
| 410 Gone | Vardı, kalıcı olarak kaldırıldı | Kullanımdan kaldırılmış kaynak |
| 412 Precondition Failed | Koşul sağlanmadı | If-Match uyuşmadı |
| 415 Unsupported Media Type | Gövde biçimi desteklenmiyor | XML gönderildi, JSON bekleniyor |
| 422 Unprocessable Content | Biçim doğru, anlam geçersiz | İş kuralına takılan doğrulama |
| 429 Too Many Requests | Hız sınırı aşıldı | Retry-After eklenir |
| 500 Internal Server Error | Beklenmeyen sunucu hatası | Yakalanmamış istisna |
| 502 / 503 / 504 | Vekil 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-Afterile 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ık | Yön | İşi |
|---|---|---|
Accept | İstek | İstenen biçim(ler); karşılanamazsa 406 |
Content-Type | İkisi | Gövdenin biçimi; desteklenmiyorsa 415 |
Accept-Encoding / Content-Encoding | İkisi | Sıkıştırma (gzip, br) |
Accept-Language | İstek | Dil tercihi |
Authorization | İstek | Kimlik bilgisi (Bearer, 13. hat) |
Location | Yanıt | 201'de yeni kaynağın adresi; 3xx'te hedef |
Retry-After | Yanıt | 429/503'te ne zaman tekrar denenmeli |
Allow | Yanıt | 405'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
- 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. - Yalnız başlıklar: aynı adrese
curl -I(HEAD) gönder. Gövde geldi mi?Content-Lengthne diyor? - Metot semantiği: kendi servisinde bir kaydı iki kez
PUTile güncelle, sonra iki kezPOSTile oluştur. Veritabanında kaç kayıt var? Hangisi idempotent çıktı? - 405 üret: yalnız GET destekleyen bir uca
DELETEgönder. Dönen kod ne?Allowbaşlığı geldi mi? - İçerik pazarlığı:
Accept: application/xmlile istek yap. Servisin ne döndü — 200 mü 406 mı? SonraContent-Type: text/plainile POST gönder ve 415 gördüğünü doğrula. - 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?
- Önbellek: bir GET ucuna
ETagekle. İkinci isteği-H 'If-None-Match: "v7"'ile gönder ve 304 aldığını, gövdenin gelmediğini doğrula. - Kayıp güncelleme: aynı kaydı iki terminalde oku, ikisinde de değiştir ve
sırayla
PUTet. İkinci yazma birincisini ezdi mi?If-Matchekleyip 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."
