From 658898f0d1cf15963c9d095e1834230031c0fb0d Mon Sep 17 00:00:00 2001 From: Jacky Jiang Date: Fri, 18 Sep 2020 12:03:09 +1000 Subject: [PATCH 1/2] avoid http 416 (Range Not Satisfiable Error): when request last chunk, request a range that is beyond the available content length --- papaparse.js | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/papaparse.js b/papaparse.js index e1825d14..dd5268bf 100755 --- a/papaparse.js +++ b/papaparse.js @@ -608,6 +608,9 @@ 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; if (IS_WORKER) { @@ -667,6 +670,11 @@ License: MIT if (this._config.chunkSize) { 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); } @@ -692,8 +700,17 @@ License: MIT 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); }; @@ -704,13 +721,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); From 5cfa35f5659d085e43ac9c6c3570996301aabb69 Mon Sep 17 00:00:00 2001 From: Jacky Jiang Date: Fri, 18 Sep 2020 12:37:28 +1000 Subject: [PATCH 2/2] avoid http 416 error when config chunk is larger than file size --- papaparse.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/papaparse.js b/papaparse.js index dd5268bf..ad9817f2 100755 --- a/papaparse.js +++ b/papaparse.js @@ -612,6 +612,19 @@ License: MIT // 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) { this._nextChunk = function() @@ -667,7 +680,7 @@ 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) { @@ -694,6 +707,15 @@ 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(); @@ -702,7 +724,7 @@ License: MIT var size = getFileSize(xhr); - if(size !== -1) { + if (size !== -1) { // we only update totalContentSize when we can get valid content range header totalContentSize = size; }