İçeriğe geç
academia.sh

Ders 09 / 15

HTTP İstemcisi

Bir HTTP istemcisinin istek ve yanıt nesneleri, taşıma katmanı kapanışı, zaman aşımı istisnası, yeniden deneme ve varsayılan değerle kurulur; servisin bildirdiği fiyatın ve bu savunmaların sonucu kimin değiştirdiğine etkisi ölçülür.

Yayın Güncelleme

Bu derste

  • İstek ve yanıtı yöntem, URL, başlık, gövde ve durum kodundan oluşan nesneler olarak modellemek
  • Taşıma katmanını bir kapanış olarak ayırıp zaman aşımını istisnayla göstermek
  • Yeniden deneme ve varsayılan değerin sonucu nasıl sınırladığını ölçmek
  • Servisin bildirdiği değerin sahibinin üçüncü taraf olduğunu, savunmanın bu sahibi her durumda değiştirmediğini göstermek
İçindekiler

Bir önceki derste ortağın gönderdiği metni ayrıştırdık. O metnin bize nasıl ulaştığını ise göstermedik. Bu derste ulaştırma işini ele alıyoruz ve bir şeyi baştan söylüyoruz: bu derste ağa çıkılmaz. Ders kodu ağa bağlanmayan bir ortamda çalışır; bu yüzden gerçek bir servis yerine, isteği alıp yanıt döndüren bir kapanış kullanılır. Soru şudur: servis yanıt vermezse istemci ne yapar ve sonucu kim belirler?

Ölçü ekseni yine sonucun sahibidir. Karar sırası değişmez: istemci, üçüncü taraf, ortam; hiçbiri değiştiremiyorsa sunucu. Bu derste ağırlıklı taraf üçüncü taraftır: bağlandığımız servis. Çekirdek dosya aynen gelir.

<?php
// taraf.php — bu sonucu kim değiştirebiliyor?
declare(strict_types=1);

// Kodun dışındaki taraflar, karar sırasıyla: her istek, her dış çağrı, her dağıtım.
const DIS_TARAFLAR = ['istemci', 'üçüncü taraf', 'ortam'];

/** Sonucu bir kez sapmasız, sonra her dış tarafın sapmasıyla ayrı ayrı hesaplar. */
function sina(callable $sonuc, array $sapmalar): array {
    $taban = $sonuc([]);
    $degisti = [];
    foreach (DIS_TARAFLAR as $taraf) {
        if (array_key_exists($taraf, $sapmalar)) {
            $degisti[$taraf] = $sonuc([$taraf => $sapmalar[$taraf]]) !== $taban;
        }
    }
    return $degisti;
}

/** Sonucu değiştirebilen ilk dış taraf; hiçbiri değiştiremiyorsa sunucu. */
function taraf(array $degisti): string {
    foreach (DIS_TARAFLAR as $t) {
        if ($degisti[$t] ?? false) return $t;
    }
    return 'sunucu';
}

function tarafRaporu(string $ad, callable $sonuc, array $sapmalar): string {
    $degisti = sina($sonuc, $sapmalar);
    $satirlar = [];
    foreach ($degisti as $t => $d) $satirlar[] = $t . ($d ? ' değiştirdi' : ' değiştiremedi');
    $sonTaraf = taraf($degisti);
    echo "$ad → $sonTaraf\n    ", implode(' · ', $satirlar), "\n";
    return $sonTaraf;
}

VE21. Servis, bir ürünün fiyatını JSON olarak bildirir; varsayılan fiyat 12.5’tir. VE22. Üçüncü tarafın sapması iki biçimdedir: servis fiyatı 15.5 olarak bildirir ya da ilk çağrıda yanıt vermez ve zaman aşımı olur. VE23. Taşıma katmanı tek bir kapanıştır: bir istek nesnesi alır, bir yanıt nesnesi döndürür ya da zaman aşımı istisnası fırlatır. VE24. Zaman aşımı yalnız ilk çağrıda olur; ikinci çağrıda servis yanıt verir.

İstek, Yanıt ve Taşıma Katmanı

Bir HTTP isteği dört parçadan oluşur: yöntem, URL, başlıklar ve gövde. Gerçek bir yanıtın da başlıkları vardır; bu derste yanıt yalnız durum kodu ve gövdeyle modellenir. Bunları iki küçük sınıfla modelleyebiliriz. Taşıma katmanı, yani isteği gerçekten bir soket üzerinden yollayan parça, istemciden ayrı tutulur ve bir kapanış olarak verilir. Bu ayrımın iki yararı vardır: istemci mantığı ağsız denenebilir ve taşıma katmanı değiştiğinde istemci değişmez.

<?php
// istemci.php — istek, yanıt, zaman aşımı ve fiyat istemcisi
declare(strict_types=1);

final class Istek {
    public function __construct(
        public readonly string $yontem,
        public readonly string $url,
        public readonly array $basliklar = [],
        public readonly string $govde = '',
    ) {}
}

final class Yanit {
    public function __construct(
        public readonly int $durum,
        public readonly string $govde,
    ) {}
}

final class ZamanAsimi extends RuntimeException {}

function servisKur(?string $sapma): callable {
    $cagri = 0;
    return function (Istek $istek) use ($sapma, &$cagri): Yanit {
        $cagri++;
        if ($sapma === 'zaman' && $cagri === 1) {
            throw new ZamanAsimi('servis yanıt vermedi');
        }
        $fiyat = $sapma === 'yeni' ? 15.5 : 12.5;
        return new Yanit(200, json_encode(['fiyat' => $fiyat]));
    };
}

function fiyatAl(callable $tasima, int $deneme = 1, ?float $varsayilan = null): ?float {
    $istek = new Istek('GET', 'https://servis.test/urun/k1', ['Accept' => 'application/json']);
    for ($i = 0; $i < $deneme; $i++) {
        try {
            $yanit = $tasima($istek);
            return (float) json_decode($yanit->govde, true, flags: JSON_THROW_ON_ERROR)['fiyat'];
        } catch (ZamanAsimi) {
            continue;
        }
    }
    return $varsayilan;
}

servis.test adı yalnız bir örnek alan adıdır; kapanış onu hiç çözümlemez. ZamanAsimi sınıfı, taşıma katmanının “yanıt gelmedi” durumunu bir istisna olarak taşır. fiyatAl() fonksiyonu isteği oluşturur, taşıma kapanışına verir, yanıt gövdesini ayrıştırır ve zaman aşımında yeniden dener; deneme hakkı bitince varsayılan değeri döndürür. Önce üç temel durumu görelim.

<?php
// temel.php — istek nesnesi ve üç çağrı
declare(strict_types=1);
require 'istemci.php';

$istek = new Istek('GET', 'https://servis.test/urun/k1', ['Accept' => 'application/json']);
echo $istek->yontem, ' ', $istek->url, "\n";
echo 'başlık: ', json_encode($istek->basliklar), "\n";

$yanit = servisKur(null)($istek);
echo 'durum: ', $yanit->durum, ', gövde: ', $yanit->govde, "\n";

var_dump(fiyatAl(servisKur(null)));
var_dump(fiyatAl(servisKur('zaman')));
var_dump(fiyatAl(servisKur('zaman'), 2));
var_dump(fiyatAl(servisKur('zaman'), 1, 10.0));
GET https://servis.test/urun/k1
başlık: {"Accept":"application\/json"}
durum: 200, gövde: {"fiyat":12.5}
float(12.5)
NULL
float(12.5)
float(10)

Servis sağlıklıyken fiyat 12.5 geldi. İlk çağrıda zaman aşımı olan bir serviste tek denemeyle ve varsayılan değer olmadan sonuç NULL; fiyat bilinmiyor. İki denemeyle ikinci çağrı yanıt verdi ve fiyat geldi. Tek deneme ve varsayılan değerle fiyat, servisin söylemediği 10.0 oldu. Üç savunmanın üç ayrı sonucu var; hangisinin doğru olduğu uygulamanın kararıdır.

Servisin Bildirdiği Fiyat

Servis fiyatı 15.5 olarak bildirirse istemci bunu olduğu gibi aktarır. İstemci fiyatı hesaplamaz, yalnız taşır. Bu durumda sonuç, kodun hiçbir satırı değişmeden, değişir.

<?php
// fiyatsonuc.php — servisin bildirdiği fiyat kimin?
declare(strict_types=1);
require 'taraf.php';
require 'istemci.php';

tarafRaporu('fiyat', fn(array $s) => (string) fiyatAl(servisKur($s['üçüncü taraf'] ?? null)), ['üçüncü taraf' => 'yeni']);
fiyat → üçüncü taraf
    üçüncü taraf değiştirdi

Fiyatı değiştiren servistir; istemci, ortam ya da sunucu değiştirmedi. Dışarıdan alınan bir değerin sahibi, değeri yazan taraftır.

Sınırlayıcı Ölçüm

Zaman aşımı olursa iki savunma vardır: yeniden denemek ve varsayılan değere dönmek. İkisi de sonucu sınırlar, yani sonucun en kötü hâlini belirler. Soru şudur: bu savunmalar sonucun sahibini değiştirir mi? Bunu aynı sapmayla, yani servisin ilk çağrıda yanıt vermemesiyle, üç yapılandırmada ölçelim.

<?php
// savunma.php — zaman aşımında sonucun sahibi
declare(strict_types=1);
require 'taraf.php';
require 'istemci.php';

function sonuc(array $s, int $deneme, ?float $varsayilan): string {
    return var_export(fiyatAl(servisKur($s['üçüncü taraf'] ?? null), $deneme, $varsayilan), true);
}

$sapma = ['üçüncü taraf' => 'zaman'];
tarafRaporu('savunmasız', fn(array $s) => sonuc($s, 1, null), $sapma);
tarafRaporu('varsayılan değer', fn(array $s) => sonuc($s, 1, 10.0), $sapma);
tarafRaporu('yeniden deneme', fn(array $s) => sonuc($s, 2, null), $sapma);
savunmasız → üçüncü taraf
    üçüncü taraf değiştirdi
varsayılan değer → üçüncü taraf
    üçüncü taraf değiştirdi
yeniden deneme → sunucu
    üçüncü taraf değiştiremedi

Savunmasız istemcide zaman aşımı sonucu NULL yaptı ve sahibi üçüncü taraftır. Varsayılan değer NULL yerine 10.0 koydu; sonuç bir sınıra oturdu ama servisin yanıt vermemesi hâlâ sonucu değiştirdi, dolayısıyla sahip yine üçüncü taraftır. Sınır yazıldı, sahip değişmedi. Yeniden denemede ise ikinci çağrı sağlıklı yanıt verdi ve sonuç sapmasız ölçümle aynı çıktı; bu savunma sahibi sunucuya taşıdı. İkisi aynı sapmaya karşı kurulmuş savunmalardır, ama etkileri farklıdır. Savunmanın sonucu sınırlaması ile sahibi değiştirmesi ayrı iki şeydir: varsayılan değer yalnız birincisini yaptı.

Bir uyarı gerekir: yeniden denemenin sahibi sunucuya taşıması, yalnız servisin ikinci çağrıda yanıt vermesi koşuluna bağlıdır. VE24 bunu varsayar. Servis sürekli yanıt vermiyorsa yeniden deneme de sonucu kurtarmaz.

Özet

  • İstek yöntem, URL, başlık ve gövdeden; yanıt durum kodu ve gövdeden oluşur. Taşıma katmanı bir kapanış olarak ayrıldığında istemci ağsız denenebilir.
  • Zaman aşımı bir istisnadır; istemci onu yakalar ve deneme hakkına ya da varsayılan değere bakar.
  • Servisin bildirdiği fiyatın sahibi üçüncü taraftır; istemci değeri yalnız taşır.
  • Varsayılan değer sonucu sınırlar ama sahibi değiştirmez; bu derste ölçülen sahip yine üçüncü taraftır.
  • Yeniden deneme, servis ikinci çağrıda yanıt verdiği sürece sahibi sunucuya taşır.

Sonraki Adım

İstemci artık yanıtı alıyor, ayrıştırıyor ve zaman aşımına karşı savunuyor. Ama yanıttan çıkan değer uygulamanın içine girdiğinde ve istemcinin kendi girdileri hesaba katıldığında, bu değerlerin geçerli olup olmadığına kim karar verir? Sonraki konu Güvenlik: Girdi Doğrulama ve Arındırma dersi, bu soruyla başlar.

İ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