Getirme API'si ile akış istekleri

Jake Archibald
Jake Archibald

Chromium 105'te, Streams API'yi kullanarak tüm gövde kullanılabilir olmadan önce isteği başlatabilirsiniz.

Bunu şu amaçlarla kullanabilirsiniz:

  • Sunucuyu ısıtın. Başka bir deyişle, kullanıcı bir metin giriş alanına odaklandığında isteği başlatabilir, ardından tüm üstbilgileri kaldırın ve ardından kullanıcı "gönder"e basana kadar bekleyebilirsiniz. geri dönmesini sağlar.
  • İstemcide oluşturulan ses, video veya giriş verileri gibi verileri kademeli olarak gönderin.
  • HTTP/2 veya HTTP/3 üzerinden web yuvalarını yeniden oluşturun.

Ancak bu alt düzey bir web platformu özelliği olduğundan benim fikirlerimle sınırlı kalmayın. Belki istek akışı için çok daha heyecan verici bir kullanım alanı düşünebilirsiniz.

Demo

Verileri kullanıcıdan sunucuya nasıl akış olarak aktarabileceğinizi ve gerçek zamanlı olarak işlenebilecek verileri nasıl geri gönderebileceğinizi gösterir.

Peki, bu en hayal gücü zor bir örnek değil. Sadece basit tutmak istedim, değil mi?

Peki, bu nasıl çalışır?

Getirme akışlarının heyecan verici maceralarından önceki videolarda

Yanıt akışları bir süredir tüm modern tarayıcılarda kullanılabiliyor. Sunucudan geldikçe yanıtın bazı bölümlerine erişmenize olanak tanırlar:

const response = await fetch(url);
const reader = response.body.getReader();

while (true) {
  const {value, done} = await reader.read();
  if (done) break;
  console.log('Received', value);
}

console.log('Response fully received');

Her value, baytlık Uint8Array değerindedir. Aldığınız dizilerin sayısı ve dizilerin boyutu, ağın hızına bağlıdır. Hızlı bir bağlantı kullanıyorsanız daha az ve daha büyük "parçalar" alırsınız olabilir. Yavaş bir bağlantıdaysanız daha fazla, küçük parçalar alırsınız.

Baytları metne dönüştürmek istiyorsanız TextDecoder veya hedef tarayıcılarınız destekliyorsa daha yeni dönüşüm akışını kullanabilirsiniz:

const response = await fetch(url);
const reader = response.body.pipeThrough(new TextDecoderStream()).getReader();

TextDecoderStream, tüm bu Uint8Array parçalarını alıp dizelere dönüştüren bir dönüşüm akışıdır.

Akışlar harikadır, çünkü veriler geldikçe bunlara göre işlem yapmaya başlayabilirsiniz. Örneğin, 100 'sonuç' içeren bir liste alıyorsanız 100 sonuç içeren bir liste almak yerine ilk sonucu alır almaz görüntüleyebilirsiniz.

Her neyse, yanıt akışları, heyecan verici yeni bir konu da istek akışları.

Akış isteği gövdeleri

İsteklerin gövdeleri olabilir:

await fetch(url, {
  method: 'POST',
  body: requestBody,
});

Önceden, isteği başlatabilmek için tüm gövdesinin hazır olması gerekiyordu, ancak artık Chromium 105'te kendi ReadableStream verilerinizi sağlayabilirsiniz:

function wait(milliseconds) {
  return new Promise(resolve => setTimeout(resolve, milliseconds));
}

const stream = new ReadableStream({
  async start(controller) {
    await wait(1000);
    controller.enqueue('This ');
    await wait(1000);
    controller.enqueue('is ');
    await wait(1000);
    controller.enqueue('a ');
    await wait(1000);
    controller.enqueue('slow ');
    await wait(1000);
    controller.enqueue('request.');
    controller.close();
  },
}).pipeThrough(new TextEncoderStream());

fetch(url, {
  method: 'POST',
  headers: {'Content-Type': 'text/plain'},
  body: stream,
  duplex: 'half',
});

Yukarıda "Bu yavaş bir istek" mesajı gönderilir teker teker sunucuya gönderilir ve her kelime arasında bir saniyelik duraklama olur.

İstek gövdesinin her parçasının Uint8Array bayt olması gerekir. Bu yüzden dönüşüm işlemini kendim için pipeThrough(new TextEncoderStream()) kullanıyorum.

Kısıtlamalar

Akış istekleri, web için yeni bir güçtür, bu nedenle birkaç kısıtlama vardır:

Yarım çift yönlü mü?

Akışların bir istekte kullanılmasına izin vermek için duplex istek seçeneğinin 'half' olarak ayarlanması gerekir.

HTTP'nin az bilinen bir özelliği (ancak, bunun kime sorduğunuza bağlı olarak standart bir davranış olup olmadığı), isteği göndermeye devam ederken yanıtı almaya başlayabilmenizdir. Ancak, sunucular tarafından çok iyi desteklenmediği ve hiçbir tarayıcı tarafından desteklenmediği için çok az bilinmektedir.

Tarayıcılarda, sunucu daha erken yanıt gönderse bile isteğin gövdesi tamamen gönderilene kadar yanıt hiçbir zaman kullanılabilir hale gelmez. Bu, tüm tarayıcı getirme işlemleri için geçerlidir.

Bu varsayılan kalıp "yarım çift yönlü" olarak bilinir. Ancak, Deno'daki fetch gibi bazı uygulamalar varsayılan olarak "tam çift yönlü" olarak ayarlandı akış getirmeleri için kullanılır, yani istek tamamlanmadan önce yanıt kullanılabilir hale gelebilir.

Bu uyumluluk sorununu çözmek için tarayıcılarda duplex: 'half' akış gövdesine sahip isteklerde belirtilmelidir.

duplex: 'full', gelecekte akış istekleri ve akış dışı istekler için tarayıcılarda desteklenebilir.

Bu arada, çift yönlü iletişimin bir sonraki en iyi yöntemi, akış isteğiyle bir getirme işlemi, daha sonra akış yanıtını almak için başka bir getirme işlemi yapmaktır. Sunucunun bu iki isteği ilişkilendirmek için URL'deki bir kimlik gibi bir yönteme ihtiyacı vardır. Demo bu şekilde çalışır.

Kısıtlanmış yönlendirmeler

Bazı HTTP yönlendirmesi biçimleri, tarayıcının isteğin gövdesini başka bir URL'ye yeniden göndermesini gerektirir. Bunu desteklemek için tarayıcının akışın içeriğini arabelleğe alması gerekir, bu da noktayı bozar ve bunu yapmaz.

Bunun yerine, isteğin bir akış gövdesi varsa ve yanıt 303 dışında bir HTTP yönlendirmesiyse getirme reddedilir ve yönlendirme izlenmez.

303 yönlendirmelerine, yöntemi açıkça GET olarak değiştirdikleri ve istek gövdesini sildikleri için izin verilir.

CORS gerektirir ve bir yayın öncesi tetikler

Akış isteklerinin gövdesi vardır, ancak Content-Length üstbilgisi yoktur. Bu yeni bir istek türüdür, dolayısıyla CORS gerekir ve bu istekler her zaman bir ön kontrol tetikler.

Akış no-cors isteklerine izin verilmiyor.

HTTP/1.x üzerinde çalışmaz

Bağlantı HTTP/1.x ise getirme işlemi reddedilir.

Bunun nedeni, HTTP/1.1 kurallarına göre istek ve yanıt gövdelerinin bir Content-Length üstbilgisi göndermesi ve böylece diğer tarafın ne kadar veri alacağını bilmesi veya mesajın biçimini parçalı kodlama kullanacak şekilde değiştirmesidir. Parçalı kodlamada, gövde her biri kendi içerik uzunluğuna sahip parçalara bölünür.

Yığınlı kodlama, HTTP/1.1 yanıtları söz konusu olduğunda oldukça yaygındır ancak istekler söz konusu olduğunda çok nadirdir. Bu nedenle uyumluluk riski yüksektir.

Olası sorunlar

Bu, yeni bir özelliktir ve günümüzde internette sık kullanılmamaktadır. Dikkat etmeniz gereken bazı sorunlar aşağıda belirtilmiştir:

Sunucu tarafında uyumsuzluk

Bazı uygulama sunucuları akış isteklerini desteklemez. Bunun yerine, herhangi bir isteği görmenize izin vermeden önce isteğin tamamının alınmasını bekler. Bu durum, yanlışlık anlamına gelir. Bunun yerine, NodeJS veya Deno gibi akışı destekleyen bir uygulama sunucusu kullanın.

Ancak, henüz macerayı atamadınız. NodeJS gibi uygulama sunucusu genellikle "ön uç sunucusu" olarak adlandırılan ve bir CDN'nin arkasında yer alan başka bir sunucunun arkasında yer alır. Bunlardan herhangi biri, isteği zincirdeki bir sonraki sunucuya vermeden önce arabelleğe almaya karar verirse istek akışı avantajını kaybedersiniz.

Kontrolünüz dışındaki uyumsuzluk

Bu özellik yalnızca HTTPS üzerinden çalıştığından, kullanıcı ile aranızdaki proxy'ler konusunda endişelenmenize gerek yoktur ancak kullanıcı, makinesinde bir proxy çalıştırıyor olabilir. Bazı internet koruma yazılımları, tarayıcı ile ağ arasında giden her şeyi izlemesine olanak tanımak için bunu yapar ve bu yazılımın istek gövdelerini arabelleğe aldığı durumlar olabilir.

Buna karşı koruma sağlamak istiyorsanız "özellik testi" oluşturabilirsiniz. yukarıdaki demoya benzer şekilde, akışı kapatmadan bazı verileri aktarmayı deneyebilirsiniz. Sunucu verileri alırsa farklı bir getirme işlemiyle yanıt verebilir. Bu durumda, istemcinin uçtan uca akış isteklerini desteklediğini anlarsınız.

Özellik algılama

const supportsRequestStreams = (() => {
  let duplexAccessed = false;

  const hasContentType = new Request('', {
    body: new ReadableStream(),
    method: 'POST',
    get duplex() {
      duplexAccessed = true;
      return 'half';
    },
  }).headers.has('Content-Type');

  return duplexAccessed && !hasContentType;
})();

if (supportsRequestStreams) {
  // …
} else {
  // …
}

Merak ediyorsanız, özellik algılama özelliğinin işleyiş şekli aşağıda açıklanmıştır:

Tarayıcı belirli bir body türünü desteklemiyorsa nesnede toString() yöntemini çağırır ve sonucu gövde olarak kullanır. Bu nedenle, tarayıcı istek akışlarını desteklemiyorsa istek gövdesi, "[object ReadableStream]" dizesi olur. Bir dize gövde olarak kullanıldığında Content-Type üstbilgisini kolayca text/plain;charset=UTF-8 olarak ayarlar. Böylece, bu başlık ayarlanmışsa tarayıcının istek nesnelerindeki akışları desteklemediğini biliriz ve erken çıkış yapabiliriz.

Safari, istek nesnelerindeki akışları destekler ancak fetch ile kullanılmasına izin vermez. Bu nedenle, Safari'nin şu anda desteklemediği duplex seçeneği test edilir.

Yazılabilir akışlarla kullanma

WritableStream olduğunda akışlarla çalışmak bazen daha kolay olabilir. Bunu bir "identity" [kimlik] kullanarak yapabilirsiniz. akış. Bağımsız değişken olmadan bir TransformStream oluşturarak aşağıdakilerden birini oluşturabilirsiniz:

const {readable, writable} = new TransformStream();

const responsePromise = fetch(url, {
  method: 'POST',
  body: readable,
});

Artık yazılabilir akışa gönderdiğiniz her şey isteğin bir parçası olacak. Böylece akışları birlikte oluşturabilirsiniz. Aşağıda, verilerin bir URL'den getirildiği, sıkıştırıldığı ve başka bir URL'ye gönderildiği komik bir örnek verilmiştir:

// Get from url1:
const response = await fetch(url1);
const {readable, writable} = new TransformStream();

// Compress the data from url1:
response.body.pipeThrough(new CompressionStream('gzip')).pipeTo(writable);

// Post to url2:
await fetch(url2, {
  method: 'POST',
  body: readable,
});

Yukarıdaki örnekte, rastgele verileri gzip ile sıkıştırmak için sıkıştırma akışları kullanılmaktadır.