İçeriğe geç
academia.sh

Ders 02 / 16

Durum Kodları

Durum satırının ilk basamağı bir yönergedir: aracı gövdeyi hiç açmadan saklama, yönlendirme ve yeniden deneme kararlarını ondan çıkarır.

İçindekiler

Önceki ders isteğin alanlarını ölçtü: yöntem, yol, konak. Yanıtın başlık alanlarına bakıldı ama ilk satırı bilerek dışarıda bırakıldı. Aracının bir yanıt üzerinde alacağı ilk karar o satırdadır ve satırın taşıdığı şey üç basamaklı bir sayıdır.

Bu dersin sorusu şudur: aracı, yanıtın gövdesini hiç açmadan, yalnız o sayının ilk basamağına bakarak ne kadar ileri gidebilir. Gövdeyi de açan bir aracı aynı kararlara mı varır — varıyorsa fazladan okuduğu baytın karşılığı nedir.

Durum Satırı

Yanıt, bir durum satırıyla başlar. Aşağıdaki döküm öğretim amaçlıdır ve çalıştırılmaz:

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 128

HTTP/1.1 301 Moved Permanently
Location: /olcum/kuzey/guncel

HTTP/1.1 503 Service Unavailable
Retry-After: 30

Satırın üç parçası vardır: protokolün adı ve sürümü, üç basamaklı durum kodu, ve kodun insan okuması için yazılmış kısa karşılığı. Aracının işine yarayan tek parça ortadaki sayıdır; sağdaki metin bilgilendirmedir ve karara girmez.

Sayının kendisi de bütün olarak gerekmez. İlk basamak kodun durum sınıfını (status class) verir ve aracının okuduğu asıl şey budur.

Sınıfın Kendisi Bir Yönergedir

Sınıf Anlamı Aracıya söylediği
1xx Bilgilendirme Ara yanıt; iletilir, saklanmaz
2xx Başarı Yanıt saklamaya uygundur
3xx Yönlendirme Kaynağın adresi değişti
4xx İstemci hatası Aynı istek aynı sonucu verir, yeniden denemek boşunadır
5xx Sunucu hatası Yöntem etkisizse istek yeniden denenebilir

Bu tablonun kritik özelliği, sağ sütunun kodun kendisini değil sınıfını kullanmasıdır. Aracı 404 ile 400 arasındaki farkı bilmek zorunda değildir; ikisi de dördüncü sınıftandır ve ikisinde de yeniden deneme boşunadır. Uygulamayı bilmeyen bir katmanın bilebileceği en üst ayrıntı düzeyi budur ve protokol o düzeyi ilk basamağa yerleştirmiştir.

Birinci sınıf, kurgunun ölçüm kümesine girmez ve bunun bir nedeni vardır: 1xx bir ara yanıttır, alışverişi bitirmez. Aracı onu ne saklar ne de üzerine karar kurar; olduğu gibi iletir ve nihai yanıtı beklemeyi sürdürür. Ölçülen şey alışveriş başına alınan karar olduğuna göre, alışverişi bitirmeyen bir yanıt sayıma giremez.

Web API Tasarımı kursunun kaynak tasarımı konusu hangi kodun seçilmesi gerektiğini ölçer: gevşek bir eşlemenin istek sayısını nasıl artırdığını sayar. Burada seçim verilidir; ölçülen şey, verilmiş bir sınıfın aracıya kaç kararı aldırdığıdır.

Üç Karar

Önceki dersin beş kararı yerinde kalır. Durum satırı üç karar daha açar:

Karar Gerçeği belirleyen
saklamaya_uygun Yanıt gerçekten başarılı mı
adres_degisti Kaynak gerçekten başka bir adrese mi taşındı
yeniden_denenebilir Sunucu tarafında mı başarısız oldu ve yöntem etkisiz mi

Kâhinin gerçeği bunları yanıtın gerçek sınıfından okur. Sunucunun bildirdiği kod ise ayrı bir alandır ve ikisi her zaman uyuşmaz: kurguda birkaç sunucu hatası 200 diye bildirilir. Bu, kursun her derste hatırlanan okumasının bu dersteki biçimidir — iletide yazanı doğrulayan hiçbir şey yoktur.

Karar kümesi kırk alışveriş ve sekiz karardan, yani üç yüz yirmi karardan oluşur. Ölçülebilen en küçük fark 1/320=0,00311/320 = 0{,}0031’dir; bundan küçük bir fark iddia edilmez.

Ölçümün varsayımları:

  • HA7 — Önceki dersin kırk alışverişi ve beş kararı olduğu gibi alınır; üzerine ayrı bir üreteçle durum kodu atanır ve ilk beş kararın gerçeği değişmez.
  • HA8 — Durum kodları sabit bir listeden çekilir ve birinci sınıf kümeye girmez: ara yanıt alışverişi bitirmez, bitmeyen alışveriş de sayıma giremez.
  • HA9 — Kâhinin gerçeği yanıtın gerçek sınıfıdır; sunucunun bildirdiği kod ayrı bir alandır ve kurguda kimi sunucu hatası 200 diye bildirilir. Bildirimi doğrulayan bir düzenek yoktur.
  • HA10 — İki rejim karşılaştırılır: aracı ya yalnız durum satırını okur, ya gövdeyi de açar. Aradaki tek fark sınıfın nereden okunduğudur — kodun ilk basamağından ya da gövdeden.
  • HA11 — Gövde, çözülmüş bir yanıt içeriğini temsil eden sabit yapıda bir alandır ve gerçek sınıfı doğru taşır; gövdeyi açmanın baytı bu alanın sayımından gelir.
  • HA12 — Tur bilançosu bir zamanlama ölçümü değil bir sayımdır: üçüncü sınıf yanıtın eklediği ve dördüncü sınıf yanıtın sildiği turlar alışveriş sayısından hesaplanır.

Çekirdeğin Genişletilmesi

Çekirdek önceki dersinkiyle aynıdır; üzerine durum_ata işlevi, üç yeni karar ve gövdeyi gören ikinci bir görünürlük demeti eklenir. kahin işlevinin ilk beş satırına dokunulmamıştır.

TOHUM = 20260809
GUVENLI = {"GET", "HEAD"}
ETKISIZ = {"GET", "HEAD", "PUT", "DELETE"}
YONTEMLER = ["GET", "GET", "GET", "HEAD", "POST", "PUT", "DELETE"]
YOLLAR = ["/olcum/kuzey", "/olcum/yamac", "/ozet", "/kayit", "/oturum"]
DURUMLAR = [200, 200, 200, 201, 204, 301, 400, 404, 500, 503, 503]
TEMEL = ("saklanabilir", "paylasilabilir", "taze", "yinelenebilir",
         "yonlendirilebilir")
DURUM_KARARLARI = ("saklamaya_uygun", "adres_degisti", "yeniden_denenebilir")
KARARLAR = TEMEL + DURUM_KARARLARI
ACIK = ("yontem", "yol", "konak", "ozel_imi", "dogrulayici")
SATIR = ACIK + ("durum",)
SATIR_GOVDE = SATIR + ("govde",)


def uretec(tohum: int):
    d = tohum % 2147483646 + 1

    def r(n: int) -> int:
        nonlocal d
        d = (d * 48271) % 2147483647
        return d % n
    return r


def alisverisler(sayi: int = 40, tohum: int = TOHUM) -> list[dict]:
    r, liste = uretec(tohum), []
    for i in range(sayi):
        yontem, yol = YONTEMLER[r(7)], YOLLAR[r(5)]
        ozel = yol == "/oturum" or r(5) == 0
        degisti = r(3) == 0
        liste.append({
            "no": i + 1, "yontem": yontem, "yol": yol,
            "ozel": ozel, "degisti": degisti,
            "ozel_imi": ozel and r(4) != 0,
            "damga_atladi": degisti and r(6) == 0,
        })
    return liste


def durum_ata(liste: list[dict], tohum: int = TOHUM + 101) -> list[dict]:
    r = uretec(tohum)
    for a in liste:
        kod = DURUMLAR[r(11)]
        gercek = kod // 100
        yanilti = gercek == 5 and r(3) == 0   # sunucu, hatayı 200 diye bildirdi
        a["gercek_sinif"] = gercek
        a["durum"] = 200 if yanilti else kod
        a["yanilti"] = yanilti
    return liste


def kahin(a: dict) -> dict:
    return {
        "saklanabilir": a["yontem"] in GUVENLI and not a["ozel"],
        "paylasilabilir": not a["ozel"],
        "taze": not a["degisti"],
        "yinelenebilir": a["yontem"] in ETKISIZ,
        "yonlendirilebilir": True,
        "saklamaya_uygun": a["gercek_sinif"] == 2,
        "adres_degisti": a["gercek_sinif"] == 3,
        "yeniden_denenebilir": a["gercek_sinif"] == 5 and a["yontem"] in ETKISIZ,
    }


def ileti(a: dict, gorunur: tuple) -> dict:
    tam = {"yontem": a["yontem"], "yol": a["yol"], "konak": "istasyon.ornek",
           "ozel_imi": a["ozel_imi"],
           "dogrulayici": a["degisti"] and not a["damga_atladi"],
           "durum": a["durum"],
           "govde": {"sinif": a["gercek_sinif"], "yol": a["yol"],
                     "ayrinti": "cozumlenmis yanit govdesi",
                     "izleme": f"iz-{a['no']:04d}"}}
    return {k: v for k, v in tam.items() if k in gorunur}


def bayt(i: dict) -> int:
    return sum(len(k) + len(str(v)) + 4 for k, v in i.items())


def araci(i: dict) -> dict:
    k = {}
    if "yontem" in i:
        k["yinelenebilir"] = i["yontem"] in ETKISIZ
    if "ozel_imi" in i:
        k["paylasilabilir"] = not i["ozel_imi"]
        if "yontem" in i:
            k["saklanabilir"] = i["yontem"] in GUVENLI and not i["ozel_imi"]
    if "dogrulayici" in i:
        k["taze"] = not i["dogrulayici"]
    if "konak" in i:
        k["yonlendirilebilir"] = True
    if "durum" in i:
        sinif = i["govde"]["sinif"] if "govde" in i else i["durum"] // 100
        k["saklamaya_uygun"] = sinif == 2
        k["adres_degisti"] = sinif == 3
        if "yontem" in i:
            k["yeniden_denenebilir"] = sinif == 5 and i["yontem"] in ETKISIZ
    return k


def olc(alis: list[dict], gorunur: tuple, kararlar: tuple = KARARLAR) -> tuple:
    d = y = e = b = 0
    for x in alis:
        gercek, verilen = kahin(x), araci(ileti(x, gorunur))
        for karar in kararlar:
            if karar not in verilen:
                e += 1
            elif verilen[karar] == gercek[karar]:
                d += 1
            else:
                y += 1
        b += bayt(ileti(x, gorunur))
    return d, y, e, b


def dagilim(alis: list[dict], alan: str) -> dict:
    d: dict = {}
    for x in alis:
        d[x[alan]] = d.get(x[alan], 0) + 1
    return dict(sorted(d.items()))

araci işlevindeki tek satır bütün ayrımı taşır: gövde görünürse sınıf gövdeden, görünmezse durum // 100 ile satırdan okunur. İki aracı arasındaki fark yalnız budur.

İki Aracı

alis = durum_ata(alisverisler())
print("gerçek sınıf dağılımı:", dagilim(alis, "gercek_sinif"))
print("bildirilen kod dağılımı:", dagilim(alis, "durum"))
print("sınıfını yanlış bildiren yanıt:", sum(x["yanilti"] for x in alis))
t = olc(alis, SATIR, TEMEL)
print(f"temel beş karar (iki rejimde de aynı): doğru {t[0]}, yanlış {t[1]}")
print()
print(f"{'rejim':<12s} {'doğru':>6s} {'yanlış':>7s} {'alınamayan':>11s} {'bayt':>6s}")
for ad, g in (("satır", SATIR), ("satır+gövde", SATIR_GOVDE)):
    d, y, e, b = olc(alis, g)
    print(f"{ad:<12s} {d:6d} {y:7d} {e:11d} {b:6d}")
gerçek sınıf dağılımı: {2: 22, 3: 3, 4: 7, 5: 8}
bildirilen kod dağılımı: {200: 16, 201: 5, 204: 4, 301: 3, 400: 4, 404: 3, 500: 1, 503: 4}
sınıfını yanlış bildiren yanıt: 3
temel beş karar (iki rejimde de aynı): doğru 195, yanlış 5

rejim         doğru  yanlış  alınamayan   bayt
satır           309      11           0   4004
satır+gövde     315       5           0   8044

İlk okunacak sütun alınamayandır ve iki rejimde de sıfırdır. Durum satırı görünür olduğu sürece aracı üç yeni kararın üçünü de alır; sunucuya sormaz, tur borçlanmaz. Önceki dersin beş kararı da yerinde durur: iki rejimde de 195 doğru, 5 yanlış.

Aradaki bütün fark üç yüz yirmi kararın altısındadır. Satır rejimi 309, gövdeyi de açan rejim 315 doğru karar verir. Buna karşılık gövdeyi açmak baytı 4004’ten 8044’e çıkarır.

Gövdenin Kazandırdığı

Altı kararın nereden geldiğine bakmak gerekir.

print(f"{'':<20s} {'satır':>14s} {'satır+gövde':>16s}")
print(f"{'karar':<20s} {'doğru':>6s} {'yanlış':>7s} {'doğru':>8s} {'yanlış':>7s}")
for karar in DURUM_KARARLARI:
    s = [0, 0, 0, 0]
    for x in alis:
        gercek = kahin(x)
        for j, g in enumerate((SATIR, SATIR_GOVDE)):
            v = araci(ileti(x, g))
            s[2 * j + (0 if v[karar] == gercek[karar] else 1)] += 1
    print(f"{karar:<20s} {s[0]:6d} {s[1]:7d} {s[2]:8d} {s[3]:7d}")

print()
print(f"{'no':>3s} {'yöntem':<7s} {'yol':<14s} {'gerçek':>7s} {'bildirilen':>11s} "
      f"{'etkisiz':>8s}")
for x in alis:
    if x["yanilti"]:
        print(f"{x['no']:3d} {x['yontem']:<7s} {x['yol']:<14s} {x['gercek_sinif']:7d} "
              f"{x['durum']:11d} {str(x['yontem'] in ETKISIZ):>8s}")

ds, dg = olc(alis, SATIR), olc(alis, SATIR_GOVDE)
kazanc, bedel = dg[0] - ds[0], dg[3] - ds[3]
print()
print(f"gövdenin kazandırdığı karar {kazanc}, ödettiği bayt {bedel}, "
      f"karar başına {bedel / kazanc:.1f} bayt")
print("karar kümesi iki rejimde ayrılan alışveriş:",
      sum(1 for x in alis if araci(ileti(x, SATIR)) != araci(ileti(x, SATIR_GOVDE))))

eklenen = sum(x["gercek_sinif"] == 3 for x in alis)
kacinilan = sum(x["gercek_sinif"] == 4 for x in alis)
print(f"yönlendirmenin eklediği tur {eklenen}, boşuna denemeden kaçınılan tur "
      f"{kacinilan}, kırk alışverişte net {eklenen - kacinilan}")
                              satır      satır+gövde
karar                 doğru  yanlış    doğru  yanlış
saklamaya_uygun          37       3       40       0
adres_degisti            40       0       40       0
yeniden_denenebilir      37       3       40       0

 no yöntem  yol             gerçek  bildirilen  etkisiz
  2 PUT     /olcum/yamac         5         200     True
  8 GET     /olcum/kuzey         5         200     True
  9 GET     /ozet                5         200     True

gövdenin kazandırdığı karar 6, ödettiği bayt 4040, karar başına 673.3 bayt
karar kümesi iki rejimde ayrılan alışveriş: 3
yönlendirmenin eklediği tur 3, boşuna denemeden kaçınılan tur 7, kırk alışverişte net -4

Kırk alışverişin otuz yedisinde iki aracı aynı karar kümesini üretir. Gövdeyi açmak bu otuz yedide tek bir kararı bile değiştirmez; yalnız bayt okur. Ayrılan üç alışverişte kazanılan karar sayısı altı, ödenen bayt 4040 — karar başına 673,3 bayt.

Karşılaştırma ölçüsü de burada belirir. Durum satırı, aracıya kırk alışverişin kırkında üç kararı birden verir ve bunun için alışveriş başına yalnız on iki bayt okutur. Gövde, üç yüz yirmi kararın altısını düzeltir ve bunun için alışveriş başına yüz bir bayt daha okutur. Sınıfın ilk basamağa konması bir yer kazanma tercihi değildir; aracının okuması gereken en küçük veriyi yanıtın en başına koyma tercihidir.

Karar Bedava, İşlem Bir Tur

adres_degisti kararı iki rejimde de kırk kararın kırkında doğrudur; dersin en ucuz kararı budur. Ucuzluk, kararın sonucunun da ucuz olduğu anlamına gelmez.

Üçüncü sınıf bir yanıt istenen içeriği vermez, yalnız başka bir adres bildirir. Aracı bunu iletiden okur ve kimseye sormaz — karar fazladan tur istemez. Ama kararın gereğini yapmak, yani yeni adrese gitmek, tam bir gidiş–dönüştür. Kırk alışverişin üçü üçüncü sınıftandır ve bu üç alışveriş hiçbir başlık alanının kaldıramayacağı üç tur ekler; turu kaldırmanın tek yolu adresi değiştirmektir.

Dördüncü sınıf ters yönde çalışır. Aracı bir 4xx gördüğünde aynı isteğin yeniden gönderilmesinin aynı sonucu vereceğini bilir ve göndermez. Kırk alışverişin yedisi dördüncü sınıftandır; bu yedi alışverişte tek bir basamak, sonucu değişmeyecek bir turu peşinen atmaktan kurtarır. Kırk alışverişteki bilanço eksi dörttür: yönlendirme üç tur ekler, istemci hatası yedi turu siler.

Beşinci sınıfın bilançosu yönteme bağlıdır ve bu yüzden tek başına yazılamaz. Sekiz alışverişin kaçının yeniden denenebileceğini belirleyen şey durum sınıfı değil, önceki dersin ölçtüğü yöntem alanıdır; karar iki alanın kesişiminde durur.

Bu üç satır, ölçünün neden alan sayısı değil tur olduğunu gösterir. Durum satırı tek bir alandır ve gövdeye hiç dokunmadan kırk alışverişte üç tur ekler, yedi tur siler.

Sınıfını Yanlış Bildiren Yanıt

Üç alışverişte sunucu, sunucu tarafında oluşan bir hatayı 200 diye bildirmiştir. Gövdede gerçek durum yazılıdır; satırda yazmaz.

Satır rejiminde bu üç yanıt iki kararı birden bozar. saklamaya_uygun üçünde de yanlış çıkar: aracı başarısız bir yanıtı önbelleğine koyar ve aynı hatayı sonraki isteklere de dağıtır. yeniden_denenebilir de üçünde yanlış çıkar, çünkü üçünün de yöntemi etkisizdir ve gerçek sınıfı bilinseydi istek yeniden gönderilebilirdi. adres_degisti bozulmaz: yanlış bildirim üçüncü sınıfa dokunmaz.

Buradaki asıl sonuç şudur: aracı yanlış karar vermedi, yanlış bildirime doğru davrandı. İlk basamağı okumak, ilk basamağın doğru yazıldığı varsayımıyla birlikte gelir. Protokolde bu varsayımı sınayan bir düzenek yoktur; gövdeyi açmak da bir çözüm değildir, çünkü gövdeyi yazan da aynı sunucudur.

Daraltma tarafı da vardır. Sunucu durum satırını doğru yazdığında aracı hiçbir şey kaybetmez; yanlış yazdığında ise kaybı gövdeyle kapatmak alışveriş başına yüz bayttan fazla tutar ve kırk alışverişin otuz yedisinde bu bayt boşa gider. Sınıfı doğru bildirmek, aracının okuması gereken veriyi en aza indiren tek şeydir.

Özet

  • Durum satırının ilk basamağı bir durum sınıfı verir; aracı saklama, yönlendirme ve yeniden deneme kararlarını kodun tamamından değil bu tek basamaktan çıkarır.
  • Sınıf tabanlı okuma, aracının kodun ayrıntısını bilmesini gerektirmez: 400 ile 404 aynı yönergeyi taşır.
  • Kırk alışveriş ve sekiz kararlık kümede satır rejimi 309 doğru, 11 yanlış karar ve 4004 bayt; gövdeyi de açan rejim 315 doğru, 5 yanlış karar ve 8044 bayt verir.
  • İki rejim kırk alışverişin otuz yedisinde aynı karar kümesini üretir; gövdenin kazandırdığı altı kararın bedeli karar başına 673,3 bayttır.
  • Kararın ucuz olması işlemin de ucuz olduğunu göstermez: yönlendirme kırk alışverişte üç tur ekler, istemci hatası yedi turu siler, bilanço eksi dörttür.
  • Sınıfını yanlış bildiren üç yanıt saklamaya_uygun ve yeniden_denenebilir kararlarını bozar; aracı yanlış karar vermez, yanlış bildirime doğru davranır.

Sonraki Adım

Durum satırı yanıtın ne olduğunu söyler. Söylemediği bir şey vardır: aynı adresin birden çok karşılığı olabilir ve sunucu bunlardan hangisini gönderdiğini durum satırında bildirmez. Aynı ölçüm kaydı ayrı biçimlerde, ayrı dillerde, ayrı kodlamalarda sunulabilir; ikisi de 200 ile döner. Sonraki ders bu seçimin yapıldığı yeri, yani başlık alanlarını ölçer ve iki soruyu birden sorar: ilgisiz alanlar eklemek aracıya karar kazandırır mı, ve seçimin hangi alana bakılarak yapıldığı bildirilmezse aracı hangi kopyayı kime verir.

İ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