diff --git a/src/source.ts b/src/source.ts index 98cdc52..b1caf1a 100644 --- a/src/source.ts +++ b/src/source.ts @@ -234,12 +234,7 @@ export class BlobSource extends Source { const { done, value } = await reader.read(); if (done) { this._orchestrator.forgetWorker(worker); - - if (worker.currentPos < worker.targetPos) { // I think this `if` should always hit? - throw new Error('Blob reader stopped unexpectedly before all requested data was read.'); - } - - break; + throw new Error('Blob reader stopped unexpectedly before all requested data was read.'); } if (worker.aborted) { @@ -441,7 +436,7 @@ export class UrlSource extends Source { let fileSize: number; if (response.status === 206) { - fileSize = this._getPartialLengthFromRangeResponse(response); + fileSize = this._getTotalLengthFromRangeResponse(response); worker = this._orchestrator.createWorker(0, Math.min(fileSize, URL_SOURCE_MIN_LOAD_AMOUNT)); } else { // Server probably returned a 200. @@ -515,14 +510,6 @@ export class UrlSource extends Source { ); } - const length = this._getPartialLengthFromRangeResponse(response); - const required = worker.targetPos - worker.currentPos; - if (length < required) { - throw new Error( - `HTTP response unexpectedly too short: Needed at least ${required} bytes, got only ${length}.`, - ); - } - if (!response.body) { throw new Error( 'Missing HTTP response body stream. The used fetch function must provide the response body as a' @@ -568,16 +555,17 @@ export class UrlSource extends Source { const { done, value } = readResult; if (done) { - this._orchestrator.forgetWorker(worker); - - if (worker.currentPos < worker.targetPos) { - throw new Error( - 'Response stream reader stopped unexpectedly before all requested data was read.', - ); + if (worker.currentPos >= worker.targetPos) { + // All data was delivered, we're good + this._orchestrator.forgetWorker(worker); + worker.running = false; + return; } - worker.running = false; - return; + // The response stopped early, before the target. This can happen if server decides to cap range + // requests arbitrarily, even if the request had an uncapped end. In this case, let's fetch the rest + // of the data using a new request. + break; } this.onread?.(worker.currentPos, worker.currentPos + value.length); @@ -597,25 +585,23 @@ export class UrlSource extends Source { } /** @internal */ - private _getPartialLengthFromRangeResponse(response: Response) { + private _getTotalLengthFromRangeResponse(response: Response) { const contentRange = response.headers.get('Content-Range'); if (contentRange) { const match = /\/(\d+)/.exec(contentRange); if (match) { return Number(match[1]); - } else { - throw new Error(`Invalid Content-Range header: ${contentRange}`); } + } + + const contentLength = response.headers.get('Content-Length'); + if (contentLength) { + return Number(contentLength); } else { - const contentLength = response.headers.get('Content-Length'); - if (contentLength) { - return Number(contentLength); - } else { - throw new Error( - 'Partial HTTP response (status 206) must surface either Content-Range or' - + ' Content-Length header.', - ); - } + throw new Error( + 'Partial HTTP response (status 206) must surface either Content-Range or' + + ' Content-Length header.', + ); } }