İçeriğe geç
academia.sh

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-Type baytları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; 4xx isteğin, 5xx sunucunun 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.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat