Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better Chunk loading handling to avoid HTTP 416 (Range Not Satisfiable) Error #832

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
58 changes: 54 additions & 4 deletions papaparse.js
Expand Up @@ -608,6 +608,22 @@ License: MIT
ChunkStreamer.call(this, config);

var xhr;
// Will update `totalContentSize` with possible total size info from Content-Range response header
// Its value will be -1 to indicate the total size info can't be retrieve from Content-Range response header (or not available)
var totalContentSize = -1;

// the `this._config.chunkSize` could be large than total size of the file
// which lead to server response 416 (Range Not Satisfiable error) on the first chunk / range request
// If this happened on the first, we should only retry request content without range header once
var shouldDisableChunk = false;

// indicate we are processing the first chunk
// if http 416 error is captured, we will disable chunk and retry the request
var isFirstChunk = true;

// indicate we have retried the request without range header (or without end range if it's not the first chunk)
// we can check this field to make sure that we only ever retry once
var hasRetriedRequestFor416 = false;

if (IS_WORKER)
{
Expand Down Expand Up @@ -664,9 +680,14 @@ License: MIT
}
}

if (this._config.chunkSize)
if (this._config.chunkSize && !shouldDisableChunk)
{
var end = this._start + this._config.chunkSize - 1; // minus one because byte range is inclusive
if(totalContentSize > 1 && end > totalContentSize - 1) {
// if valid totalContentSize (retrieved from content-range response header) is available
// and end is beyond totalContentSize, replace end with totalContentSize - 1 (as range is inclusive)
end = totalContentSize - 1;
}
xhr.setRequestHeader('Range', 'bytes=' + this._start + '-' + end);
}

Expand All @@ -686,14 +707,32 @@ License: MIT
if (xhr.readyState !== 4)
return;

if (xhr.status === 416 && isFirstChunk && !hasRetriedRequestFor416) {
isFirstChunk = false;
hasRetriedRequestFor416 = true;
shouldDisableChunk = true;
// retry once
this._nextChunk();
return;
}

if (xhr.status < 200 || xhr.status >= 400)
{
this._chunkError();
return;
}

// Use chunckSize as it may be a diference on reponse lentgh due to characters with more than 1 byte
this._start += this._config.chunkSize ? this._config.chunkSize : xhr.responseText.length;
var size = getFileSize(xhr);

if (size !== -1) {
// we only update totalContentSize when we can get valid content range header
totalContentSize = size;
}

var chunkSize = getChunkSize(xhr);

// Use chunckSize that is retrieved from content-length header if available
this._start += chunkSize !== -1 ? chunkSize : this._config.chunkSize;
this._finished = !this._config.chunkSize || this._start >= getFileSize(xhr);
this.parseChunk(xhr.responseText);
};
Expand All @@ -704,13 +743,24 @@ License: MIT
this._sendError(new Error(errorText));
};

function getChunkSize(xhr)
{
var contentLength = xhr.getResponseHeader('Content-Length');
if (contentLength === null) { // no content length, return -1 to indicate this
return -1;
}
var length = parseInt(contentLength);
return length > 0 ? length : -1;
}

function getFileSize(xhr)
{
var contentRange = xhr.getResponseHeader('Content-Range');
if (contentRange === null) { // no content range, then finish!
return -1;
}
return parseInt(contentRange.substring(contentRange.lastIndexOf('/') + 1));
var size = parseInt(contentRange.substring(contentRange.lastIndexOf('/') + 1));
return size > 0 ? size : -1; // this also exclude the situation that size is NaN
}
}
NetworkStreamer.prototype = Object.create(ChunkStreamer.prototype);
Expand Down