@statewalker/webrun-http-streams 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +73 -10
- package/package.json +1 -1
- package/src/fetch.ts +84 -12
package/dist/index.js
CHANGED
|
@@ -1278,7 +1278,7 @@ async function fetchOverDuplex(call, request, options = {}) {
|
|
|
1278
1278
|
else await release?.();
|
|
1279
1279
|
return new Response(null, responseInit);
|
|
1280
1280
|
}
|
|
1281
|
-
return new Response(
|
|
1281
|
+
return new Response(responseBody(respBody, request.signal, release), responseInit);
|
|
1282
1282
|
}
|
|
1283
1283
|
/**
|
|
1284
1284
|
* Wrap a `(Request) => Promise<Response>` handler as a `Duplex` so it can be
|
|
@@ -1409,15 +1409,78 @@ function trackingEnd(source) {
|
|
|
1409
1409
|
settle
|
|
1410
1410
|
};
|
|
1411
1411
|
}
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1412
|
+
/**
|
|
1413
|
+
* How long a cancelled body waits for the read in flight before it abandons the call anyway.
|
|
1414
|
+
* The read's answer tells a body that has ended (the call is finished: an upload the handler
|
|
1415
|
+
* reads after answering goes on) from one that has not; a peer that sends nothing more never
|
|
1416
|
+
* answers it. A body whose end comes later than this after the cancel is abandoned too, so a
|
|
1417
|
+
* handler still streaming its response and still reading the upload then loses the upload.
|
|
1418
|
+
*/
|
|
1419
|
+
const CANCEL_GRACE_MS = 1e3;
|
|
1420
|
+
/**
|
|
1421
|
+
* The response body as a stream that the request's signal can end at any time.
|
|
1422
|
+
*
|
|
1423
|
+
* An abort errors the stream with the abort's reason at once — a read waiting
|
|
1424
|
+
* for a peer that sends nothing more rejects then, not when a next chunk
|
|
1425
|
+
* arrives, which may be never — and abandons the call (`release`), so on a
|
|
1426
|
+
* multiplexed transport its port is given back whether or not anyone still
|
|
1427
|
+
* reads the body. The listener is removed once the body ends, however it ends:
|
|
1428
|
+
* read to its end, cancelled, failed or aborted. A body nobody reads or cancels
|
|
1429
|
+
* keeps it until the signal aborts (and keeps the call open until then anyway).
|
|
1430
|
+
* A cancel abandons the call too — unless the read in flight comes back with the
|
|
1431
|
+
* body's end, which finishes it instead (see `cancel`).
|
|
1432
|
+
*/
|
|
1433
|
+
function responseBody(body, signal, release) {
|
|
1434
|
+
const it = body[Symbol.asyncIterator]();
|
|
1435
|
+
let controller;
|
|
1436
|
+
let ended = false;
|
|
1437
|
+
let reading = false;
|
|
1438
|
+
const end = () => {
|
|
1439
|
+
ended = true;
|
|
1440
|
+
signal?.removeEventListener("abort", onAbort);
|
|
1441
|
+
};
|
|
1442
|
+
const onAbort = () => {
|
|
1443
|
+
if (ended) return;
|
|
1444
|
+
end();
|
|
1445
|
+
try {
|
|
1446
|
+
controller.error(abortReason(signal));
|
|
1447
|
+
} catch {}
|
|
1448
|
+
release?.();
|
|
1449
|
+
};
|
|
1450
|
+
return new ReadableStream({
|
|
1451
|
+
start(c) {
|
|
1452
|
+
controller = c;
|
|
1453
|
+
if (signal?.aborted) onAbort();
|
|
1454
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
1455
|
+
},
|
|
1456
|
+
async pull(c) {
|
|
1457
|
+
try {
|
|
1458
|
+
reading = true;
|
|
1459
|
+
const { done, value } = await it.next().finally(() => {
|
|
1460
|
+
reading = false;
|
|
1461
|
+
});
|
|
1462
|
+
if (ended) return;
|
|
1463
|
+
if (done) {
|
|
1464
|
+
end();
|
|
1465
|
+
c.close();
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
c.enqueue(value);
|
|
1469
|
+
} catch (err) {
|
|
1470
|
+
if (ended) return;
|
|
1471
|
+
end();
|
|
1472
|
+
c.error(err);
|
|
1473
|
+
}
|
|
1474
|
+
},
|
|
1475
|
+
cancel() {
|
|
1476
|
+
end();
|
|
1477
|
+
const returned = Promise.resolve(it.return?.()).catch(() => {});
|
|
1478
|
+
if (!reading) return;
|
|
1479
|
+
const grace = setTimeout(() => void release?.(), CANCEL_GRACE_MS);
|
|
1480
|
+
grace.unref?.();
|
|
1481
|
+
returned.finally(() => clearTimeout(grace));
|
|
1482
|
+
}
|
|
1483
|
+
});
|
|
1421
1484
|
}
|
|
1422
1485
|
//#endregion
|
|
1423
1486
|
//#region src/duplex-site-builder.ts
|
package/package.json
CHANGED
package/src/fetch.ts
CHANGED
|
@@ -155,7 +155,7 @@ export async function fetchOverDuplex(
|
|
|
155
155
|
else await release?.();
|
|
156
156
|
return new Response(null, responseInit);
|
|
157
157
|
}
|
|
158
|
-
return new Response(
|
|
158
|
+
return new Response(responseBody(respBody, request.signal, release), responseInit);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
/**
|
|
@@ -329,16 +329,88 @@ function trackingEnd(source: AsyncIterable<Uint8Array>): {
|
|
|
329
329
|
return { source: tracked, settle };
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
-
|
|
333
|
-
|
|
332
|
+
/**
|
|
333
|
+
* How long a cancelled body waits for the read in flight before it abandons the call anyway.
|
|
334
|
+
* The read's answer tells a body that has ended (the call is finished: an upload the handler
|
|
335
|
+
* reads after answering goes on) from one that has not; a peer that sends nothing more never
|
|
336
|
+
* answers it. A body whose end comes later than this after the cancel is abandoned too, so a
|
|
337
|
+
* handler still streaming its response and still reading the upload then loses the upload.
|
|
338
|
+
*/
|
|
339
|
+
const CANCEL_GRACE_MS = 1_000;
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* The response body as a stream that the request's signal can end at any time.
|
|
343
|
+
*
|
|
344
|
+
* An abort errors the stream with the abort's reason at once — a read waiting
|
|
345
|
+
* for a peer that sends nothing more rejects then, not when a next chunk
|
|
346
|
+
* arrives, which may be never — and abandons the call (`release`), so on a
|
|
347
|
+
* multiplexed transport its port is given back whether or not anyone still
|
|
348
|
+
* reads the body. The listener is removed once the body ends, however it ends:
|
|
349
|
+
* read to its end, cancelled, failed or aborted. A body nobody reads or cancels
|
|
350
|
+
* keeps it until the signal aborts (and keeps the call open until then anyway).
|
|
351
|
+
* A cancel abandons the call too — unless the read in flight comes back with the
|
|
352
|
+
* body's end, which finishes it instead (see `cancel`).
|
|
353
|
+
*/
|
|
354
|
+
function responseBody(
|
|
355
|
+
body: AsyncIterable<Uint8Array>,
|
|
334
356
|
signal: AbortSignal | undefined,
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
357
|
+
release: (() => Promise<void>) | undefined,
|
|
358
|
+
): ReadableStream<Uint8Array> {
|
|
359
|
+
const it = body[Symbol.asyncIterator]();
|
|
360
|
+
let controller!: ReadableStreamDefaultController<Uint8Array>;
|
|
361
|
+
let ended = false;
|
|
362
|
+
let reading = false;
|
|
363
|
+
const end = () => {
|
|
364
|
+
ended = true;
|
|
365
|
+
signal?.removeEventListener("abort", onAbort);
|
|
366
|
+
};
|
|
367
|
+
const onAbort = () => {
|
|
368
|
+
if (ended) return;
|
|
369
|
+
end();
|
|
370
|
+
try {
|
|
371
|
+
controller.error(abortReason(signal as AbortSignal));
|
|
372
|
+
} catch {
|
|
373
|
+
/* already closed or errored */
|
|
374
|
+
}
|
|
375
|
+
void release?.();
|
|
376
|
+
};
|
|
377
|
+
return new ReadableStream<Uint8Array>({
|
|
378
|
+
start(c) {
|
|
379
|
+
controller = c;
|
|
380
|
+
if (signal?.aborted) onAbort();
|
|
381
|
+
else signal?.addEventListener("abort", onAbort, { once: true });
|
|
382
|
+
},
|
|
383
|
+
async pull(c) {
|
|
384
|
+
try {
|
|
385
|
+
reading = true;
|
|
386
|
+
const { done, value } = await it.next().finally(() => {
|
|
387
|
+
reading = false;
|
|
388
|
+
});
|
|
389
|
+
if (ended) return; // aborted or cancelled while this read waited
|
|
390
|
+
if (done) {
|
|
391
|
+
end();
|
|
392
|
+
c.close();
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
c.enqueue(value);
|
|
396
|
+
} catch (err) {
|
|
397
|
+
if (ended) return;
|
|
398
|
+
end();
|
|
399
|
+
c.error(err);
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
cancel() {
|
|
403
|
+
end();
|
|
404
|
+
const returned = Promise.resolve(it.return?.()).catch(() => {});
|
|
405
|
+
if (!reading) return; // `it` is at rest: its return abandons the call (releasingOnExit)
|
|
406
|
+
// A read is in flight, and `it.return()` is queued behind it: that read's answer decides.
|
|
407
|
+
// The body's end → the call is finished, not abandoned, so an upload the handler reads
|
|
408
|
+
// after answering goes on; a chunk → the return then abandons the call. A peer that never
|
|
409
|
+
// answers (sends nothing more, for good) would hold the call forever: abandon it after a
|
|
410
|
+
// grace, without waiting in `cancel()` either way.
|
|
411
|
+
const grace = setTimeout(() => void release?.(), CANCEL_GRACE_MS);
|
|
412
|
+
(grace as { unref?: () => void }).unref?.();
|
|
413
|
+
void returned.finally(() => clearTimeout(grace));
|
|
414
|
+
},
|
|
415
|
+
});
|
|
344
416
|
}
|