İçeriğe geç
academia.sh

Ders 10 / 17

HTTP İstekleri

İstek ve yanıt nesnelerinin yapısı, durum kodunun hata sayılmaması, gövdenin tek kez okunabilmesi ve akış halindeki gövdenin eşzamansız yinelemeyle parça parça işlenmesi.

İçindekiler

Buraya kadar ölçüm kaynağı hep aynı süreçte, zamanlayıcıyla taklit edildi. Gerçek bir kaynak süreç dışındadır: istek ağ üzerinden gider, yanıt parça parça döner ve arada kontrol edilemeyen bir gecikme vardır.

Bu ders kaynağı süreç dışına taşır. İnternet Nasıl Çalışır kursunda protokol düzeyinde anlatılan istek–yanıt yapısının JavaScript’teki karşılığı ele alınacak; ölçüm akışı da bir HTTP hizmetinden okunur hale gelecek.

Örneklerde kullanılan sunucu, aynı programın içinde geri döngü adresinde başlatılır. Bağlantı noktası olarak sıfır verilir; ana ortam boş bir bağlantı noktası seçer ve seçilen değer değişkendir, bu yüzden hiçbir yerde yazdırılmaz.

İstek ve Yanıt Birer Nesnedir

İstek ve yanıt, ağ katmanının altında kalan ayrıntılardan bağımsız iki nesneyle temsil edilir. İkisi de ağ olmadan da oluşturulabilir; bu, yapılarının incelenmesini kolaylaştırır.

const istek = new Request("http://ornek.gecersiz/olcum/B2", {
  method: "POST",
  headers: { "content-type": "application/json", "x-istemci": "olcum-toplayici" },
  body: JSON.stringify({ istasyon: "B2", deger: 19.8 }),
});

console.log("yöntem:", istek.method);
console.log("yol:", new URL(istek.url).pathname);
console.log("başlık:", istek.headers.get("x-istemci"));
console.log("gövde:", await istek.json());

const yanit = new Response(JSON.stringify({ durum: "kaydedildi" }), {
  status: 201,
  headers: { "content-type": "application/json" },
});

console.log("yanıt durumu:", yanit.status, yanit.ok);
console.log("yanıt gövdesi:", await yanit.json());
yöntem: POST
yol: /olcum/B2
başlık: olcum-toplayici
gövde: { istasyon: 'B2', deger: 19.8 }
yanıt durumu: 201 true
yanıt gövdesi: { durum: 'kaydedildi' }

Üç nokta dikkat çeker. Başlıklar sıradan bir nesne değil, ad–değer erişimi sunan ayrı bir yapıdır. Gövdeyi okuyan yöntemler (json, text) söz döndürür; çünkü gövde henüz tam olarak gelmemiş olabilir. Durum kodu ile ok alanı birlikte taşınır: ok, durum kodunun başarı aralığında olup olmadığını söyler.

Ağ Hatası ile Başarısız Durum Kodu Ayrıdır

İstek gönderen fonksiyon, yalnız isteği iletemediğinde reddeder: bağlantı kurulamadı, ad çözümlenemedi, bağlantı koptu. Sunucu bir yanıt döndürdüyse — durum kodu ne olursa olsun — dönen söz yerine gelir.

import { createServer } from "node:http";

const KAYNAK = {
  A1: { gecikme: 30, deger: 21.4 },
  B2: { gecikme: 10, deger: 19.8 },
  C3: { gecikme: 20, deger: 23.1 },
};

const sunucu = createServer((istek, yanit) => {
  const yol = new URL(istek.url, "http://yerel").pathname;
  const istasyon = yol.replace("/olcum/", "");
  const kayit = KAYNAK[istasyon];

  if (kayit === undefined) {
    yanit.writeHead(404, { "content-type": "application/json" });
    yanit.end(JSON.stringify({ hata: "bilinmeyen istasyon", istasyon }));
    return;
  }

  setTimeout(() => {
    yanit.writeHead(200, { "content-type": "application/json" });
    yanit.end(JSON.stringify({ istasyon, deger: kayit.deger }));
  }, kayit.gecikme);
});

await new Promise((karar) => sunucu.listen(0, "127.0.0.1", karar));
const taban = `http://127.0.0.1:${sunucu.address().port}`;

const yanit = await fetch(`${taban}/olcum/B2`);
console.log("durum:", yanit.status, "başarılı mı:", yanit.ok);
console.log("içerik türü:", yanit.headers.get("content-type"));
console.log("gövde:", await yanit.json());

const bulunamadi = await fetch(`${taban}/olcum/Z9`);
console.log("durum:", bulunamadi.status, "başarılı mı:", bulunamadi.ok);
console.log("gövde:", await bulunamadi.json());

sunucu.close();
durum: 200 başarılı mı: true
içerik türü: application/json
gövde: { istasyon: 'B2', deger: 19.8 }
durum: 404 başarılı mı: false
gövde: { hata: 'bilinmeyen istasyon', istasyon: 'Z9' }

Bilinmeyen istasyon isteği bir hata atmadı; 404 durum kodlu geçerli bir yanıt döndü. Bu, sık yapılan bir hatanın kaynağıdır: try/catch bloğu tek başına başarısız istekleri yakalamaz.

Doğru kalıp, durum kodunu açıkça denetleyip başarısız yanıtı hataya çevirmektir. Denetim ayrı bir işleve alındığında her çağrı noktasında tekrarlanması gerekmez:

function denetle(yanit) {
  if (!yanit.ok) throw new Error(`istek başarısız: ${yanit.status}`);
  return yanit;
}

const basarili = new Response("veri", { status: 200 });
const basarisiz = new Response("bulunamadı", { status: 404 });

console.log("başarılı yanıt geçti:", denetle(basarili).status);

try {
  denetle(basarisiz);
} catch (hata) {
  console.log("denetim yakaladı:", hata.message);
}
başarılı yanıt geçti: 200
denetim yakaladı: istek başarısız: 404

Ayrım anlamlıdır: ağ hatası “isteğin sunucuya ulaşıp ulaşmadığı bilinmiyor” demektir; başarısız durum kodu ise “ulaştı, sunucu değerlendirdi ve reddetti” demektir. İkisi farklı kurtarma stratejileri gerektirir — bu ayrım yeniden deneme dersinde belirleyici olacak.

Gövde Bir Kez Okunur

Yanıt gövdesi bir akıştır; okunduğunda tükenir. İkinci okuma girişimi hata üretir.

const yanit = new Response(JSON.stringify({ istasyon: "B2", deger: 19.8 }), {
  headers: { "content-type": "application/json" },
});

console.log("gövde kullanıldı mı:", yanit.bodyUsed);
console.log("birinci okuma:", await yanit.json());
console.log("gövde kullanıldı mı:", yanit.bodyUsed);

try {
  await yanit.json();
} catch (hata) {
  console.log("ikinci okuma hatası:", hata.constructor.name);
}
gövde kullanıldı mı: false
birinci okuma: { istasyon: 'B2', deger: 19.8 }
gövde kullanıldı mı: true
ikinci okuma hatası: TypeError

Bu kısıt, gövdenin bellekte tutulmadığının doğrudan sonucudur. Aynı gövdeye iki kez bakılması gerekiyorsa iki yol vardır: sonucu bir değişkende saklamak ya da yanıtı clone ile çoğaltmak. Çoğaltma, iki okuyucunun aynı veriyi ayrı ayrı tüketmesi demektir ve bellekte kopya tutar; bu nedenle varsayılan seçim sonucu saklamaktır.

Gövdeyi Akış Halinde İşlemek

Gövdenin tamamını beklemek, büyük yanıtlarda hem gecikme hem bellek maliyeti doğurur. Gövde, eşzamansız bir yineleyicidir: önceki dersteki for await döngüsü doğrudan uygulanır.

import { createServer } from "node:http";

const sunucu = createServer((istek, yanit) => {
  yanit.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
  const satirlar = ["A1;21.4", "B2;19.8", "C3;23.1"];
  let dizin = 0;

  function sonrakiSatir() {
    if (dizin === satirlar.length) {
      yanit.end();
      return;
    }
    yanit.write(`${satirlar[dizin]}\n`);
    dizin += 1;
    setTimeout(sonrakiSatir, 20);
  }
  sonrakiSatir();
});

await new Promise((karar) => sunucu.listen(0, "127.0.0.1", karar));
const taban = `http://127.0.0.1:${sunucu.address().port}`;

const yanit = await fetch(`${taban}/akis`);
const cozucu = new TextDecoder();
let parcaSayisi = 0;

for await (const parca of yanit.body) {
  parcaSayisi += 1;
  console.log("parça", parcaSayisi, "—", cozucu.decode(parca, { stream: true }).trim());
}
console.log("toplam parça:", parcaSayisi);

sunucu.close();
parça 1 — A1;21.4
parça 2 — B2;19.8
parça 3 — C3;23.1
toplam parça: 3

Sunucu üç satırı yirmişer milisaniye arayla yazdı; istemci her satırı geldiği anda işledi, son satırı beklemedi. Ölçüm akışı böylece gerçek anlamda akış haline geldi: veri tamamlanmadan işlenmeye başlanabiliyor.

İki ayrıntı taşınabilirlik için önemlidir. Birincisi, parça sınırları veri sınırlarıyla aynı değildir; ağ, satırları böler ya da birleştirebilir. Bu örnekte her yazma arasına gecikme konduğu için parçalar satırlarla örtüştü, ama genel durumda örtüşmez. Doğru uygulama, gelen parçaları bir tampona ekleyip satır sonu arayan bir çözümleyicidir.

İkincisi, çözücüye verilen stream seçeneği, çok baytlı bir karakterin iki parçaya bölünmesi durumunu doğru ele alır. Bilgisayarlar Nasıl Çalışır kursundaki karakter kodlaması konusunun doğrudan pratik karşılığıdır: bayt sınırı ile karakter sınırı aynı şey değildir.

Kalan Boşluk

Bu dersin programlarında bir varsayım gizliydi: sunucu er ya da geç yanıt verir. Vermezse await sonsuza kadar bekler; ne sonuç gelir ne hata. Söz konusu boşluk söz kavramı dersinde işaret edilmişti ve hâlâ açık.

Aynı biçimde, başlatılan bir isteğin sonucundan vazgeçmenin de bir yolu yok: birleştirici dersinde görüldüğü gibi, beklenmemek iptal edilmek değildir. İstek sürer, yanıt gelir, bellek tutulur.

Özet

  • İstek ve yanıt birer nesnedir; başlıklar ad–değer erişimi sunan ayrı bir yapıda tutulur ve gövde okuma yöntemleri söz döndürür.
  • İstek gönderen fonksiyon yalnız iletim başarısızlığında reddeder; başarısız durum kodu geçerli bir yanıttır ve ok alanıyla denetlenmelidir.
  • Gövde bir akıştır ve bir kez okunur; ikinci okuma hata verir.
  • Gövde eşzamansız yineleyicidir; for await ile parça parça işlenebilir.
  • Parça sınırları veri sınırlarıyla örtüşmez; çözümleme tamponla yapılmalıdır.

Sonraki Adım

Sonraki ders açık kalan iki boşluğu birlikte kapatır: bir isteğe süre sınırı koymak ve başlatılmış bir isteği gerçekten durdurmak. İptal işareti kavramı tanıtılacak, zaman aşımı bu işaret üzerine kurulacak ve yalnız beklemeyi kesen yarış tabanlı çözümün neden yetersiz kaldığı gösterilecek.

İ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