Ders 09 / 12
HTTP İstek ve Yanıtı
İsteğin ve yanıtın parçaları, yöntemlerin güvenlik ve etkisizlik özellikleri, durum kodu sınıfları ve protokolün durumsuzluğu.
İçindekiler
Önceki konu, adres satırına yazılan adın nasıl bir sunucuya vardığını kurdu. İstemci–Sunucu Modeli dersinde bu sunucuya bağlanılmış ve alışverişin iki ucu da görülmüştü. Ancak gönderilen metnin neden o biçimde olduğu açıklanmadı.
Bu ders o biçimi tanımlar. HTTP, istemci ile sunucu arasındaki alışverişin kurallarını belirleyen protokoldür ve web’in üzerine kurulduğu sözleşmedir.
Metin Tabanlı Bir Protokol
İstemci–Sunucu Modeli dersindeki sunucu, aldığı baytları olduğu gibi ekrana yazmıştı:
GET / HTTP/1.1 Host: ornek.test:8080 User-Agent: ornek-istemci Accept: */*
Bu, ikilik bir yapı değil, okunabilir bir metindir. Tasarımın bu yönde olması bir seçimdir ve bedeli vardır: metin gösterimi, aynı bilgiyi ikilik gösterimden daha çok baytla taşır ve ayrıştırılması daha pahalıdır. Karşılığında protokol, özel bir araç gerekmeden incelenebilir ve genişletilebilir olur; yeni bir başlık eklemek var olan gerçekleştirimleri bozmaz.
Satır sonları \r\n iki karakteriyle yazılır. Başlıkların bittiğini gösteren şey, boş bir
satırdır — yukarıdaki çıktının sonundaki boşluk budur.
İsteğin Yapısı
Bir istek üç bölümden oluşur.
İstek satırı, ilk satırdır ve üç alan taşır: yöntem, hedef, sürüm.
GET / HTTP/1.1
GET yapılacak işlemi, / istenen kaynağın yolunu, HTTP/1.1 istemcinin konuştuğu
protokol sürümünü bildirir. Hedefin yalnızca yol bölümü yazılır; alan adı ayrı bir
başlıkta taşınır.
Başlıklar, her satırda bir ad: değer çifti taşır. Başlık adları büyük–küçük harf
ayrımı gözetmez. Yukarıdaki üç başlığın işlevleri farklıdır: Host hangi siteye
bağlanıldığını bildirir ve Barındırma Modelleri dersinde görüldüğü gibi sanal barındırmanın
dayanağıdır; User-Agent istemciyi tanıtır; Accept istemcinin hangi içerik biçimlerini
işleyebildiğini söyler.
Gövde, boş satırdan sonra gelen isteğe bağlı veridir. GET isteklerinde genellikle
bulunmaz; veri gönderen yöntemlerde bulunur.
Yanıtın Yapısı
Yanıt aynı düzeni izler, yalnızca ilk satırı farklıdır.
HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 73 Connection: close <!doctype html> <html lang="tr"><body><h1>Ornek Sayfa</h1></body></html>
Durum satırı sürümü, sayısal durum kodunu ve okunabilir açıklamasını taşır. Açıklama metni bilgilendirme amaçlıdır; kararlar sayıya göre verilir.
Content-Length başlığı gövdenin kaç bayt olduğunu bildirir. Bu başlık bir ayrıntı değil,
protokolün işlemesi için zorunlu bir bilgidir: alıcının gövdenin nerede bittiğini bilmesi
gerekir. Bağlantı bir bayt akışıdır ve akışta doğal bir sınır yoktur. Uzunluk önceden
bilinmiyorsa gövde parçalar hâlinde, her parçanın başında boyu yazılarak gönderilir.
Content-Type gövdenin nasıl yorumlanacağını söyler. Buradaki charset=utf-8 bölümü,
Bilgisayarlar Nasıl Çalışır kursundaki karakter kodlamaları dersinde kurulan soruyu
yanıtlar: baytların hangi kurala göre karakterlere çevrileceği alıcının tahminine
bırakılmaz, açıkça bildirilir.
Yöntemler
Yöntem, kaynak üzerinde yapılacak işlemi bildirir. Yöntemleri ayıran iki özellik vardır ve bu özellikler istemcilerin davranışını belirler.
Güvenli (safe) bir yöntem sunucuda değişiklik yapmaz; yalnızca okur. Etkisiz (idempotent) bir yöntem, bir kez ya da birden çok kez uygulandığında aynı sonucu bırakır. Her güvenli yöntem etkisizdir; tersi doğru değildir.
| Yöntem | İşlevi | Güvenli | Etkisiz |
|---|---|---|---|
GET |
Kaynağı getirir | Evet | Evet |
HEAD |
Yalnızca başlıkları getirir | Evet | Evet |
POST |
Veri gönderir, yeni kaynak oluşturabilir | Hayır | Hayır |
PUT |
Kaynağı verilen içerikle değiştirir | Hayır | Evet |
DELETE |
Kaynağı siler | Hayır | Evet |
Bu özellikler kuramsal değildir. Bir yanıt alınamadığında isteğin yinelenip
yinelenemeyeceği doğrudan buradan çıkar: etkisiz bir istek güvenle tekrarlanabilir,
POST isteği tekrarlanırsa aynı işlem iki kez yapılabilir. DELETE yöntemi ikinci
çağrıda farklı bir durum kodu döndürse de sunucunun durumu aynı kalır; etkisizlik, dönen
kodun değil bırakılan durumun özelliğidir.
Bu özelliklerin sunucu tarafından korunması gerekir; protokol bunu zorlayamaz. Durumu
değiştiren bir GET işleyicisi yazmak sözdizimsel olarak olanaklıdır ve protokole
aykırıdır.
HEAD yöntemi, GET ile aynı başlıkları döndürür ancak gövde göndermez. Bir kaynağın
boyutunu veya varlığını içeriği indirmeden öğrenmek için kullanılır.
Durum Kodları
Durum kodu üç basamaklıdır ve ilk basamağı sonucun sınıfını verir.
| Sınıf | Anlamı |
|---|---|
1xx |
Bilgilendirme; işlem sürüyor |
2xx |
Başarı |
3xx |
Yönlendirme; kaynak başka yerde |
4xx |
İstemci hatası; istek kusurlu |
5xx |
Sunucu hatası; istek geçerliydi ama sunucu yerine getiremedi |
4xx ile 5xx arasındaki ayrım işletme açısından belirleyicidir: birincisi isteği
düzeltmeyi, ikincisi sunucuyu incelemeyi gerektirir.
Aşağıdaki sunucu üç farklı durum kodu üretir ve bir yönlendirme tanımlar:
from http.server import BaseHTTPRequestHandler, HTTPServer class Isleyici(BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" def log_message(self, bicem: str, *arg: object) -> None: pass def _gonder(self, durum: int, govde: bytes, ek: list[tuple[str, str]] = []) -> None: self.send_response_only(durum) self.send_header("Content-Type", "text/plain; charset=utf-8") self.send_header("Content-Length", str(len(govde))) for ad, deger in ek: self.send_header(ad, deger) self.end_headers() self.wfile.write(govde) def do_GET(self) -> None: if self.path == "/eski": self._gonder(301, b"", [("Location", "/yeni")]) elif self.path == "/yeni": self._gonder(200, b"yeni adresteki icerik\n") else: self._gonder(404, b"bulunamadi\n") def do_POST(self) -> None: uzunluk = int(self.headers.get("Content-Length", "0")) govde = self.rfile.read(uzunluk) self._gonder(201, b"alinan govde: " + govde + b"\n") HTTPServer(("127.0.0.1", 8080), Isleyici).serve_forever()
Üç yol sorgulandığında dönen kodlar:
for yol in /yeni /eski /yok; do printf "%-6s " "$yol" curl -s -o /dev/null -w "%{http_code}\n" --resolve ornek.test:8080:127.0.0.1 "http://ornek.test:8080$yol" done
/yeni 200 /eski 301 /yok 404
Yönlendirme yanıtının kendisi incelendiğinde, kaynağın yeni yerinin bir başlıkta taşındığı görülür:
curl -sD - -o /dev/null --resolve ornek.test:8080:127.0.0.1 http://ornek.test:8080/eski
HTTP/1.1 301 Moved Permanently Content-Type: text/plain; charset=utf-8 Content-Length: 0 Location: /yeni
Yanıtın gövdesi boştur; taşınan bilgi Location başlığındadır. İstemci bu yanıtı alınca
yeni hedefe ikinci bir istek yapar. curl bunu kendiliğinden yapmaz; istendiğinde yapar:
curl -sL --resolve ornek.test:8080:127.0.0.1 http://ornek.test:8080/eski
yeni adresteki icerik
Tarayıcılar yönlendirmeleri kendiliğinden izler. 301 kodu taşınmanın kalıcı olduğunu
bildirir ve istemcinin eski adresi anımsamamasına izin verir; geçici taşınmalar için ayrı
kodlar vardır ve bu ayrım önemlidir, çünkü kalıcı yönlendirmeler istemcide saklanabilir.
Gövde taşıyan bir istek de aynı düzeni izler:
curl -s -X POST -d "ad=deneme" --resolve ornek.test:8080:127.0.0.1 http://ornek.test:8080/kayit
alinan govde: ad=deneme
Durumsuzluk
HTTP durumsuz (stateless) bir protokoldür: sunucu, iki istek arasında istemciye ilişkin hiçbir şey anımsamaz. Her istek kendi başına, önceki isteklerden bağımsız olarak yorumlanır.
Bu, gözlenebilir bir özelliktir. Aşağıdaki sunucu, isteğin taşıdığı sayacı okur ve bir artırılmış değeri istemciye geri verir:
from http.server import BaseHTTPRequestHandler, HTTPServer class Isleyici(BaseHTTPRequestHandler): protocol_version = "HTTP/1.1" def log_message(self, bicem: str, *arg: object) -> None: pass def do_GET(self) -> None: gelen = self.headers.get("Cookie", "") sayac = int(gelen.removeprefix("sayac=")) if gelen.startswith("sayac=") else 0 govde = f"gorulen sayac: {sayac}\n".encode() self.send_response_only(200) self.send_header("Content-Type", "text/plain; charset=utf-8") self.send_header("Content-Length", str(len(govde))) self.send_header("Set-Cookie", f"sayac={sayac + 1}") self.end_headers() self.wfile.write(govde) HTTPServer(("127.0.0.1", 8080), Isleyici).serve_forever()
Üç istek, aralarında hiçbir şey taşımadan yapılırsa:
for i in 1 2 3; do curl -s --resolve ornek.test:8080:127.0.0.1 http://ornek.test:8080/; done
gorulen sayac: 0 gorulen sayac: 0 gorulen sayac: 0
Sunucu üç kez de sıfır gördü. Kendisinde hiçbir kayıt tutmuyor.
Aynı üç istek, sunucunun verdiği değeri geri taşıyarak yapılırsa:
rm -f kavanoz.txt for i in 1 2 3; do curl -s -b kavanoz.txt -c kavanoz.txt --resolve ornek.test:8080:127.0.0.1 http://ornek.test:8080/; done
gorulen sayac: 0 gorulen sayac: 1 gorulen sayac: 2
Sunucu kodu değişmedi. Değişen tek şey, istemcinin Set-Cookie ile verilen değeri
saklayıp bir sonraki istekte Cookie başlığıyla geri göndermesidir. Durum sunucuda değil,
istekte taşınmaktadır.
Durumsuzluğun gerekçesi ölçeklenmedir. Sunucu istemciye özel bir bellek tutmadığı için, ardışık iki istek farklı makineler tarafından yanıtlanabilir; sunucu çökerse yeniden başlatıldığında kaybolan bir oturum durumu olmaz. Bedeli, her isteğin kendini tanıtmak için gereken bilgiyi yeniden taşımasıdır.
Özet
- HTTP metin tabanlı bir protokoldür; ayrıştırma maliyeti karşılığında incelenebilirlik ve genişletilebilirlik sağlar.
- İstek, istek satırı, başlıklar, boş satır ve isteğe bağlı gövdeden oluşur; yanıt aynı düzeni durum satırıyla izler.
Content-Length, bayt akışında gövdenin nerede bittiğini bildirdiği için zorunludur;Content-Typebaytların nasıl yorumlanacağını söyler.- Yöntemler güvenli ve etkisiz olma özellikleriyle ayrılır; bu özellikler bir isteğin yinelenip yinelenemeyeceğini belirler ve sunucu tarafından korunmalıdır.
- Durum kodunun ilk basamağı sınıfı verir;
4xxisteğin,5xxsunucunun düzeltilmesini gerektirir. - Protokol durumsuzdur; oturum sürekliliği sunucunun belleğiyle değil, istemcinin her istekte geri taşıdığı verilerle kurulur.
Sonraki Adım
Bu ders tek bir isteği ve tek bir yanıtı inceledi. Oysa bir sayfa açıldığında yalnızca bir istek yapılmaz: gelen belge başka kaynaklara başvurur ve her biri ayrıca istenir. Sonraki ders, adres satırına yazılan metinden ekrandaki çizime uzanan aşamaları ele alacak ve bu ek isteklerin nasıl ortaya çıktığını gerçek bir sunucunun kayıtları üzerinden gösterecek.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.