Ana içeriğe geç

Bölüm 12:HTTP Mesajının Anatomisi

9 dakikalık okumaGüncelleme:

Ağlar'a “Sen”i Katmak yazısının parçası: adres çubuğuna yazdığın tek bir isteğin peşinden bakırla fiberin fiziğine kadar inen uzun bir teknik yolculuk.

Tüm bölümler
  1. Giriş
  2. Adres Çubuğundan İsteğe
  3. Paket Anahtarlamalı Yol
  4. İsimden Adrese
  5. Önbellek, Tazelik ve Güven
  6. Port Numarası ve Soket
  7. Bağlantısız Taşıma: UDP
  8. Üç Adımlı El Sıkışma
  9. Simetrik Şifreleme
  10. Açık Anahtarlı Kriptografi
  11. TLS El Sıkışması
  12. Sertifika Zinciri
  13. HTTP Mesajının Anatomisi
  14. Durum, Çerez ve Oturum
  15. HTTP/2'den HTTP/3'e
  16. İçerik Dağıtım Ağları
  17. Diğer Uygulama Protokolleri
  18. Katmanlı Mimari
  19. Kapsülleme ve Başlıklar
  20. Paket Yakalama ve Çözümleme
  21. Ağ Arayüz Kartı
  22. Fiziksel Adresleme
  23. Hata Sezme ve CRC
  24. Adres Çözümleme: ARP
  25. Anahtarlama ve Ortam Erişimi
  26. Ağ Topolojileri
  27. Sanal Yerel Ağlar
  28. Kablosuz Erişim: 802.11
  29. Radyo Kanalı ve Kapasite
  30. Kablosuz Ağ Güvenliği
  31. Hücresel Ağlar
  32. Spektrum ve Kuşaklar
  33. Hareketlilik ve Aktarma
  34. Adres Kiralama: DHCP
  35. Adresin Anatomisi
  36. Adres Tükenmesi ve IPv6
  37. Yönlendirici Mimarisi
  38. Kuyruk Yönetimi
  39. Cihaz Yapılandırma
  40. Yönlendirme Protokolleri
  41. Kontrol Düzlemi ve SDN
  42. Ağ İzleme ve Yönetimi
  43. Alanlar Arası Yönlendirme
  44. Adres Çevirisi
  45. Ara Kutular
  46. Tünelleme ve IPsec
  47. Güvenlik Duvarları
  48. Operatör ve Metro Ağları
  49. Bitten Sinyale
  50. Çoklama Teknikleri
  51. İletim Ortamları
  52. Kablo Sonlandırma
  53. Fiber Optik İletim
  54. Bakır Erişim Ağı: DSL
  55. Pasif Optik Ağlar
  56. Kablo Erişim Ağları
  57. Denizaltı Kabloları
  58. Veri Merkezi Ağları
  59. Kapsülleme Çözme
  60. Güvenilir Aktarım
  61. Tıkanıklık Denetimi
  62. Trafik Çözümleme
  63. Gecikme Bütçesi
  64. Son Söz

Kanal kuruldu, karşı taraf doğrulandı. Göndereceğin şey ise şaşırtıcı derecede sade: birkaç satır düz metin.

Şimdiye kadar “istek” deyip içine bakmadım. O metin neye benziyor, hangi kelimesi neyi belirliyor?

Bir istek neye benziyor?

HTTP web’in uygulama katmanı protokolü ve modeli tek cümleye sığıyor: bir taraf istiyor, öbür taraf istenen nesneyi yanıt olarak yolluyor. Bütün protokolde yalnızca iki tür mesaj bulunuyor, istek ile yanıt; üçüncü bir tür aramana gerek yok.

Mesajın kendisi düz metin olarak gidiyor. HTTP/1.1’de her satır ASCII, yani gözünle okuyabilirsin. Karşı tarafın kim olduğu ad doğrulamasıyla çözülmüştü; o kanal bu metni tek harfini değiştirmeden taşıyor.

RFC 9110 mesajı dört parçaya ayırıyor. Başta mesajı tanımlayan ve yönlendiren denetim verisi duruyor. Arkasından ad-değer çiftlerinden oluşan başlık tablosu geliyor. Sonra sınırsız uzunlukta olabilen içerik akışı, en sonda da kuyruk tablosu. Bir isteğin denetim verisi ilk satırda duruyor.

O satırın biçimi sabit: metot, boşluk, hedef URL, boşluk, sürüm ve satırı kapatan CR LF. Adres çubuğuna yazdığın adresin yolu buraya giriyor. Yetkili alanı ayrı, yolu ayrı kutulara koyan ayrıştırma tam da bunun içindi.

Başlık satırları arkadan geliyor. Nerede bittiklerini satır başında duran boş bir CR LF çifti söylüyor; ondan sonrası gövde. Tamamı şuna benziyor:

GET /kitap/bolum-12 HTTP/1.1
Host: example.com
User-Agent: kendi-istemcim/1.0
Accept: text/html

Dört satır, sonra boş bir satır. Her satırın sonunda görünmeyen iki bayt daha var. Onlar da sayıya giriyor: bu istek toplam 102 bayt. İstek satırı tek başına 30 bayt tutuyor. Aynı isteğin zorunlu parçalara indirgenmiş hâli 51 bayt: istek satırı, Host başlığı ve kapanış boş satırı.

HTTP kendi taşımasını da kurmuyor. İstemci sunucunun 80. portuna bir TCP bağlantısı açıyor, mesajlar o soketten geçiyor. HTTPS’te varsayılan port 443 oluyor. Numaranın neden sabit olduğu iyi bilinen portlarda geçmişti.

Bir bağlantıya kaç isteğin sığdığı ayrı bir hesap. Kalıcı olmayan HTTP’de bir TCP bağlantısında en çok bir nesne taşınıyor. Kalıcı HTTP’de sunucu yanıtı gönderdikten sonra bağlantıyı açık bırakıyor. Bunu burada tek cümleyle bırakıyorum; kalıcı bağlantının asıl hesabı iki durak sonra tutulacak.

Bir sayfa nesnelerin toplamıydı ve her nesne ayrı bir URL ile adresleniyor. O yüzden bu metin sayfa başına bir kez değil, onlarca kez yazılıyor.

Metot ne vaat ediyor?

İstek satırının ilk kelimesi isteğin anlamını taşıyor. RFC 9110 sekiz metot tanımlıyor ve bu küme protokole gömülü; uygulama kendi metodunu icat etmiyor. Metot adı büyük-küçük harfe duyarlı, standart adlar da sözleşme gereği tamamı büyük harfle yazılıyor.

MetotNe istiyorGüvenliYinelemeye dayanıklı
GETkaynağın güncel gösteriminin aktarılmasınıevetevet
HEADaynı isteğin yalnızca başlıklarınıevetevet
OPTIONSkaynağın kabul ettiği seçeneklerin bildirilmesinievetevet
TRACEmesajın yolda ne hâle geldiğinin yansıtılmasınıevetevet
PUThedefte duranın gövdeyle değiştirilmesinihayırevet
DELETEkaynağın güncel gösterimlerinin kaldırılmasınıhayırevet
POSTgövdenin kaynağa özgü kurallarla işlenmesinihayırhayır
CONNECThedefe uçtan uca bir tünel kurulmasınıhayırhayır

Son iki sütun sözleşme. Güvenli olmak, metodun anlamının salt okunur olduğunu söylüyor. Sunucuda hiçbir şeyin değişmeyeceğini garanti etmiyor; sunucu istediği sayacı artırabilir. Söylediği şey, istemcinin değişiklik istememiş olduğu.

Yinelemeye dayanıklı metot ise aynı isteği iki kez yollamanın tek kez yollamakla aynı sonucu vermesi demek. POST ikisinin de dışında kalıyor, çünkü anlamını sunucu belirliyor.

Zorunluluk listesi kısa. Genel amaçlı her sunucu GET ve HEAD’i desteklemek zorunda; kalan altısı isteğe bağlı. HEAD’in gövdeyi hiç istememesi ucuz bir yetenek veriyor: bir dosyanın boyutunu indirmeden öğrenebiliyorsun. POST’un altına ise çok iş giriyor: form verisi, mesaj gönderme, yeni kaynak yaratma, var olan içeriğe ekleme. Yeni bir kaynak yarattıysa sunucunun 201 dönmesi ve yeni adresi Location başlığında vermesi bekleniyor.

DerinleşmeGET ile POST arasındaki fark neden teknik değil, sözleşmesel?

Aynı veriyi iki ayrı yoldan gönderebilirsin. Biri GET: veri, hedef adresin içine, soru işaretinden sonra yazılıyor. Öbürü POST: veri gövdeye giriyor ve adres temiz kalıyor. Telde ikisi de aynı sokete yazılan düz metin, ve karşı taraf ikisini de okuyabiliyor.

Aradaki fark teknik değil. Fark, o istekle birlikte neyi ilan ettiğin.

GET dediğin anda isteğin salt okunur olduğunu söylemiş oluyorsun, ve bu ilanı senden başka herkes ciddiye alıyor. Tarayıcı yanıtı önbelleğe koyabiliyor. Kullanıcı adresi yer imine ekleyebiliyor, sonra oradan tekrar girebiliyor. Arama motoru bağlantıyı kendiliğinden izleyebiliyor. Bir vekil sunucu, aynı adrese gelen ikinci isteği hiç sormadan kendi kopyasıyla karşılayabiliyor. Adresin içine yazdığın her şey de sunucu kayıtlarına düz metin olarak düşüyor.

POST dediğinde bunların hepsi kapanıyor. Tarayıcı sayfayı yenilerken “formu yeniden göndereyim mi” diye soruyor, çünkü ikinci gönderimin ikinci bir sipariş anlamına gelip gelmeyeceğini bilmesinin yolu yok.

Sözleşmeyi bozmanın bedelini de sen ödemiyorsun. Silme işini GET ile yapan bir uygulama telde geçerli bir mesaj gönderiyor ve hiçbir kural çiğnenmiş görünmüyor. Sonra bir gün site haritası çıkaran bir robot bütün bağlantıları geziyor, sırayla hepsini siliyor. Robot kusurlu değil, ilan edilmiş anlama göre davrandı.

Başlıklar neyi çözüyor?

Başlık tablosunun kuralı az. Adlar büyük-küçük harfe duyarsız, yani Host ile host aynı alan.

Host zorunlu. Tek makinede birden çok alan adına hizmet veren sunucu, hangi siteyi istediğini bununla ayırt ediyor. Aynı adres, aynı port, farklı siteler. HTTP/2 ve HTTP/3’te bu alanın yerini :authority sözde-başlığı aldı.

Gövdesi olan bir istekte iki alan daha devreye giriyor. Content-Type gövdenin ne olduğunu söylüyor ve karakter kümesi aynı satırda parametre olarak geliyor. Content-Length ise gövdenin uzunluğunu onluk tabanda bayt sayısı olarak veriyor.

POST /yorum HTTP/1.1
Host: example.net
Content-Type: application/x-www-form-urlencoded
Content-Length: 27

ad=emir&yorum=merhaba+dunya

Gövdenin nerede bittiğini o sayı çiziyor. Eksik yazarsan sunucu gelmeyecek baytları beklemeye devam ediyor. Fazla yazarsan bir sonraki isteğin ilk satırını gövdenin parçası sanıyor. Boş gövdeli bir POST’ta bile alan yazılıyor ve değeri sıfır oluyor.

Mesajın sonunda bir tablo daha var, ama çoğu zaman boş kalıyor. Kuyruk, gövde bittikten sonra eklenen alanları taşıyor: akış tamamlanmadan hesaplanamayan bir özet oraya yazılıyor.

Aynı adresten farklı gösterimler istemek

Bir adres tek bir dosyayı göstermek zorunda değil. /logo adresinin arkasında hem PNG hem WebP, hem Türkçe hem İngilizce, hem sıkıştırılmış hem ham bir gösterim durabilir. İstemci hangisini istediğini istek başlıklarıyla söylüyor ve mekanizmanın adı içerik pazarlığı.

Accept: image/webp,image/png;q=0.8,*/*;q=0.5
Accept-Language: tr,en;q=0.7
Accept-Encoding: gzip, br

Virgülle ayrılmış her seçeneğin yanında bir q değeri var — 0 ile 1 arasında bir kalite ağırlığı, yazılmazsa 1 sayılıyor. Yukarıdaki satır “WebP’yi tercih ederim, PNG de olur, gerekirse her şeye razıyım” demek. Sunucu listeye bakıp en iyi eşleşmeyi seçiyor ve cevabında hangisini seçtiğini Content-Type ile bildiriyor.

Bir de “hangi başlığa göre seçtim” sorusu var; cevabı Vary başlığında dönüyor ve gibi önbelleğin anahtarını doğrudan etkiliyor. Pazarlık, aynı adresin arkasında birden çok kopya doğurduğu için önbelleğe de haber verilmesi gerekiyor.

Dosyanın yalnızca bir parçasını istemek

İkinci bir mekanizma, isteği bayt aralığına daraltıyor:

Range: bytes=1048576-2097151

Sunucu isteği kabul ederse 206 Partial Content dönüyor ve yalnızca istenen aralığı gönderiyor; Content-Range başlığında hangi aralığı verdiğini ve dosyanın toplam boyunu yazıyor. Desteklemiyorsa 200 ile bütün dosyayı gönderiyor — istemcinin bunu anlaması için durum koduna bakması yeterli.

Bu tek başlığın üç ayrı işi var. Kesilen indirme kaldığı bayttan sürüyor. video akışı, filmi baştan indirmek yerine parça parça çekiyor. Ve bir dosyayı birden çok bağlantıyla paralel indiren araçlar, her bağlantıya farklı bir aralık veriyor.

Katmanları sayarken, ağın yedi katmanlı klasik modeli olan OSI’nin oturum katmanına bakacağız ve o katmanın işini bugün kimin yaptığını soracağız. Cevabın bir parçası şimdiden elinde: kopan bir aktarımı işaretli noktadan sürdürmek, ayrı bir katman değil, tek bir istek başlığı.

Üç basamak neyi söylüyor?

Yanıtın ilk satırı durum satırı: sürüm, üç basamaklı durum kodu ve reason phrase (gerekçe cümlesi) yan yana duruyor. Kodu okumak için gövdeyi beklemek gerekmiyor: istek gidip yanıtın ilk baytları dönene kadar geçen bir gidiş-dönüş süresi yetiyor.

Kod bir tam sayı ve geçerli aralık 100 ile 599 arası. Sınıfı ilk rakam belirliyor; son iki basamağın sınıflandırmada hiçbir rolü yok.

SınıfAnlamıBu bölümde geçenler
1xxbilgi
2xxbaşarı200, 201
3xxyönlendirme301, 304
4xxistemci hatası400, 404, 405, 410
5xxsunucu hatası500, 501, 505

İstemci tanımadığı bir kodu, o sınıfın x00 koduymuş gibi işlemek zorunda: 471 gelirse onu 400 gibi ele alıyor. Anlamını bilmiyor ama ne yapacağını biliyor. Yeni kod eklemek bu yüzden protokolü kırmıyor.

Koddan sonraki reason phrase (gerekçe cümlesi) ise yalnızca tavsiye. Yerel karşılığıyla değiştirilebilir, hiç yazılmayabilir; protokol bundan etkilenmiyor. Kararı taşıyan şey de sayının kendisi.

Asıl bilgi de birbirine yakın duran kodların arasındaki farkta. 404, kaynağın güncel bir gösteriminin bulunamadığını söylüyor. Durumun kalıcı mı geçici mi olduğunu söylemiyor; sunucu kalıcı olduğunu biliyorsa 410 tercih ediliyor. 405 ile 501 de ayrı arızalar. 405’te metot tanınıyor ama o kaynakta izinli değil ve sunucu Allow başlığıyla izinli metotları saymak zorunda. 501’de metot hiç tanınmıyor. 500 ise sunucunun beklenmedik bir durumla karşılaşıp isteği yerine getiremediği hâl. 505 istenen HTTP sürümünün desteklenmediğini bildiriyor. 301 ise nesnenin kalıcı olarak taşındığını, yeni yerinin de Location başlığında durduğunu söylüyor.

Bir kod da hiç veri taşımadığı için var. İstemci elindeki kopyanın tarihini If-Modified-Since başlığıyla isteğe yazıyor. Kaynak o tarihten sonra değişmediyse yanıt 304 oluyor ve gövde hiç gönderilmiyor. O yanıt başlık bölümü bitince bitiyor, içerik de kuyruk da taşıyamıyor. İstek ayrıca If-None-Match taşıyorsa alıcı tarihi yok saymak zorunda. Etiket üzerinden kurulan koşul daha doğru kabul ediliyor. Koşullu isteğin amacı tek cümle: kopya güncelse nesneyi hiç göndermemek.

Bütün bunları kendi makinende görebilirsin. 80. porta elle bir bağlantı açıp istek satırıyla Host başlığını yazman yetiyor. Sonuna iki kez satır sonu koyuyorsun. Belgeleme için ayrılmış bir adla deneyebilirsin:

printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' \
  | nc example.com 80 | head -12

İlk satırda sürümü, kodu ve reason phrase’i (gerekçe cümlesini) göreceksin. Altında ad-değer çiftleri sıralı duracak, sonra boş bir satır gelecek, sonra gövde başlayacak. Gördüğün şey, tarayıcının saniyede onlarca kez yazdığı metnin aynısı.

Terminalden telnet ile açılan 80. port bağlantısı: üstte elle yazılan istek satırı ve Host başlığı, altında sunucunun durum satırı, yanıt başlıkları ve gövdesi ayrı ayrı işaretlenmiş. Metindeki nc denemesinin ekrandaki karşılığı.

Özet

Peki, ne öğrendik?

  • İsteğin biçimi sabit: metot, hedef, sürüm, arkasından ad-değer çiftleri ve satır başında duran boş bir satır.
  • Metot bir vaat taşır: güvenli olan salt okunur olduğunu ilan eder, yinelemeye dayanıklı olan ikinci kez yollandığında sonucu değiştirmez.
  • Başlıklar mesajı yorumlanabilir kılar: Host hangi siteyi, Content-Type gövdenin ne olduğunu, Content-Length nerede bittiğini söyler.
  • Durum kodu üç basamakta karar taşır: sınıfı ilk rakam belirler, tanınmayan kod x00 gibi işlenir, reason phrase (gerekçe cümlesi) yalnızca tavsiyedir.

Metni kanaldan geçirdin, yanıtın ilk satırını okudun. Ama o mesajın hiçbir yerinde senin kim olduğunu söyleyen bir alan yok; sunucu da geçmiş istekler hakkında hiçbir bilgi tutmuyor. İsteği yazdın; peki sunucu seni daha önce gördüğünü nereden bilecek?

13. bölüme devam et: Durum, Çerez ve Oturum