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
okalanıyla denetlenmelidir. - Gövde bir akıştır ve bir kez okunur; ikinci okuma hata verir.
- Gövde eşzamansız yineleyicidir;
for awaitile 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.