A fetch kéréstörzs-streamelése: miért csak fél duplex?

A ReadableStream-es fetch feltöltés csökkentheti a kliens memóriaigényét, de a fél-duplex működés, a CORS és a proxyk miatt nem általános feltöltési megoldás.

Egy nagy JSON-fájl vagy a böngészőben keletkező mérési adat elküldésekor a fetch() törzse nem feltétlenül egy kész Blob vagy string. Adható neki ReadableStream is, így a kliens a keletkező bájtokat fokozatosan küldi. A lehetőség érdekes, de a duplex: "half" név könnyen félrevezet: nem kapunk vele HTTP-alapú WebSocketet.

Böngészőből szerver felé haladó, több részletből álló adatfolyam sematikus ábrája

A kérés már küldés közben él

A stream producer és fogyasztó közötti szerződés: a producer Uint8Array darabokat tesz a sorba, a fogyasztó pedig akkor kér többet, amikor tudja kezelni. A Fetch specifikáció a request body-t is ilyen streamként kezeli. Ez akkor hasznos, ha nem akarunk teljes fájlt vagy hosszú ideig gyűlő adatot memóriában tartani: kliensoldali feldolgozás után azonnal indulhat az átadás.

A minimális böngészős példa szándékosan lassú. Éles alkalmazásban a lines helyére kerülhet CSV-generátor, MediaRecorderből érkező adat vagy egy transzformált fájlfolyam.

const encoder = new TextEncoder();
const lines = ["első rekord\n", "második rekord\n", "harmadik rekord\n"];

const body = new ReadableStream({
  async start(controller) {
    for (const line of lines) {
      controller.enqueue(encoder.encode(line));
      await new Promise((resolve) => setTimeout(resolve, 250));
    }
    controller.close();
  },
});

const response = await fetch("/api/import", {
  method: "POST",
  headers: { "Content-Type": "text/plain;charset=utf-8" },
  body,
  duplex: "half",
});

A duplex opció nem dísz: stream törzsnél a Fetch implementációk ezt kérik, jelenleg egyetlen megengedett értéke a "half". Az MDN leírása szerint ez azt jelenti, hogy a böngésző a válasz feldolgozása előtt elküldi a teljes kérést. Maga a Request.duplex property ráadásul nincs minden implementációban olvasható formában kivezetve; a beállítását nem érdemes feature detectálni.

A szerver dolgozhat, a kliens még vár

A fél duplex nem azt jelenti, hogy a szervernek a feltöltés végéig tétlenül kell állnia. A szerver már az első chunkok megérkezésekor írhat fájlba, ellenőrizhet rekordokat vagy elindíthat feldolgozást. A korlát a böngésző és a válasz közé kerül: az előbbi fetch() hívás promise-a nem válik a folyam közben használható válasszá. Vagyis nem építhetünk rá kétirányú, egy kapcsolaton futó interaktív protokollt.

Ez különbség a response streamekhez képest. Egy nagy letöltés response.body-ját régóta fogyaszthatjuk fokozatosan; a request stream ennek a feltöltési oldali megfelelője, de kisebb mozgástérrel. Ha a szervernek futás közben állapotot kell visszajeleznie, két kérés praktikusabb: egy streamelt POST és egy külön SSE-, polling- vagy WebSocket-csatorna.

Éles környezetben a hálózati útvonal a feltétel

A request streaming nem általános „feltöltés gyorsító”. A támogatást külön ellenőrizni kell: az MDN a duplex-et továbbra is kísérleti technológiaként jelöli. A Chromium saját dokumentációja szerint a streamelt request CORS-os kérésnél mindig preflightot vált ki, a no-cors mód nem használható, és az ott leírt implementáció HTTP/1.x kapcsolaton elutasítja a kérést. Ezért a helyi Node-demó vagy egy közvetlen HTTP/2 origin még nem bizonyítja, hogy a CDN, reverse proxy és böngésző teljes lánca is alkalmas.

Redirectnél is könnyű meglepetésbe futni. A streamet nem lehet egyszerűen újraolvasni és elküldeni a következő címre. A Chromium útmutatója szerint a 303 kivételével a redirectes streaming fetch hibával végződik. Feltöltési végpontot ezért végleges HTTPS URL-re érdemes tenni, és nem login- vagy régióátirányítás mögé.

Mikor érdemes elővenni?

Jó jelölt a kliensoldalról generált, hosszú export, ahol az adat minden darabja önállóan értelmezhető, vagy olyan import, amelyet a szerver soronként tud feldolgozni. Nem jó jelölt a hagyományos fájlfeltöltő, ha a kompatibilitás fontosabb a memóriaoptimalizálásnál: a FormData és a megszokott multipart feltöltés sokkal egyszerűbb fallbacket ad.

A stream nem helyettesíti a feltöltési korlátokat sem. A szervernek ugyanúgy maximális bájtméretet, időkorlátot és hitelesítést kell érvényesítenie; a végtelen vagy nagyon lassú producer egyébként sokáig foglalhat kapcsolatot. Hiba esetén a kliens oldali ReadableStream producerét is le kell állítani, különben feleslegesen folytathatja a CPU- vagy I/O-munkát akkor is, amikor a hálózati kérés már meghiúsult.

A gyakorlati ellenőrzőlista rövid: legyen valódi streaminget fogadó szerveroldal, legyen a feldolgozás idempotens, mérjük meg a proxykon átmenő kérést is, és tervezzünk visszaesést normál feltöltésre. A ReadableStream ekkor nem egy látványos API-trükk, hanem memóriahatár és adatátviteli késleltetés feletti kontroll.

Források

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük