mirror of
https://github.com/arcodange-org/mediabunny.git
synced 2026-10-09 16:53:47 +02:00
UrlSource: Deal gracefully with servers that cap unbounded range requests (fixes #231)
This commit is contained in:
+21
-35
@@ -234,12 +234,7 @@ export class BlobSource extends Source {
|
|||||||
const { done, value } = await reader.read();
|
const { done, value } = await reader.read();
|
||||||
if (done) {
|
if (done) {
|
||||||
this._orchestrator.forgetWorker(worker);
|
this._orchestrator.forgetWorker(worker);
|
||||||
|
throw new Error('Blob reader stopped unexpectedly before all requested data was read.');
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (worker.aborted) {
|
if (worker.aborted) {
|
||||||
@@ -441,7 +436,7 @@ export class UrlSource extends Source {
|
|||||||
let fileSize: number;
|
let fileSize: number;
|
||||||
|
|
||||||
if (response.status === 206) {
|
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));
|
worker = this._orchestrator.createWorker(0, Math.min(fileSize, URL_SOURCE_MIN_LOAD_AMOUNT));
|
||||||
} else {
|
} else {
|
||||||
// Server probably returned a 200.
|
// 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) {
|
if (!response.body) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Missing HTTP response body stream. The used fetch function must provide the response body as a'
|
'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;
|
const { done, value } = readResult;
|
||||||
|
|
||||||
if (done) {
|
if (done) {
|
||||||
this._orchestrator.forgetWorker(worker);
|
if (worker.currentPos >= worker.targetPos) {
|
||||||
|
// All data was delivered, we're good
|
||||||
if (worker.currentPos < worker.targetPos) {
|
this._orchestrator.forgetWorker(worker);
|
||||||
throw new Error(
|
worker.running = false;
|
||||||
'Response stream reader stopped unexpectedly before all requested data was read.',
|
return;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.running = false;
|
// The response stopped early, before the target. This can happen if server decides to cap range
|
||||||
return;
|
// 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);
|
this.onread?.(worker.currentPos, worker.currentPos + value.length);
|
||||||
@@ -597,25 +585,23 @@ export class UrlSource extends Source {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @internal */
|
/** @internal */
|
||||||
private _getPartialLengthFromRangeResponse(response: Response) {
|
private _getTotalLengthFromRangeResponse(response: Response) {
|
||||||
const contentRange = response.headers.get('Content-Range');
|
const contentRange = response.headers.get('Content-Range');
|
||||||
if (contentRange) {
|
if (contentRange) {
|
||||||
const match = /\/(\d+)/.exec(contentRange);
|
const match = /\/(\d+)/.exec(contentRange);
|
||||||
if (match) {
|
if (match) {
|
||||||
return Number(match[1]);
|
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 {
|
} else {
|
||||||
const contentLength = response.headers.get('Content-Length');
|
throw new Error(
|
||||||
if (contentLength) {
|
'Partial HTTP response (status 206) must surface either Content-Range or'
|
||||||
return Number(contentLength);
|
+ ' Content-Length header.',
|
||||||
} else {
|
);
|
||||||
throw new Error(
|
|
||||||
'Partial HTTP response (status 206) must surface either Content-Range or'
|
|
||||||
+ ' Content-Length header.',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user