From 81f0a03eddd069b83562b870787db2525277f8e1 Mon Sep 17 00:00:00 2001 From: Michael Salim Date: Mon, 13 Jan 2025 00:05:36 +0000 Subject: [PATCH 1/5] Feat: Set Upload-Length if we know the length and it's deferred from the server. And allow resumeUpload if Upload-Defer-Length is set from server. --- lib/upload.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/upload.js b/lib/upload.js index 83ab57d3..cbd6cf60 100644 --- a/lib/upload.js +++ b/lib/upload.js @@ -108,7 +108,10 @@ class BaseUpload { // An array of upload URLs which are used for uploading the different // parts, if the parallelUploads option is used. - this._parallelUploadUrls = null + this._parallelUploadUrls = null + + // The remote upload resource's length is deferred + this._deferred = false } /** @@ -586,6 +589,7 @@ class BaseUpload { if (this.options.uploadLengthDeferred) { req.setHeader('Upload-Defer-Length', '1') + this._deferred = true } else { req.setHeader('Upload-Length', `${this._size}`) } @@ -704,9 +708,13 @@ class BaseUpload { return } + const deferLength = Number.parseInt(res.getHeader("Upload-Defer-Length"), 10) + this._deferred = deferLength === 1 + const length = Number.parseInt(res.getHeader('Upload-Length'), 10) if ( Number.isNaN(length) && + !this._deferred && !this.options.uploadLengthDeferred && this.options.protocol === PROTOCOL_TUS_V1 ) { @@ -821,9 +829,10 @@ class BaseUpload { // If the upload length is deferred, the upload size was not specified during // upload creation. So, if the file reader is done reading, we know the total // upload size and can tell the tus server. - if (this.options.uploadLengthDeferred && done) { + if (this._deferred && (!this.options.uploadLengthDeferred || done)) { this._size = this._offset + valueSize req.setHeader('Upload-Length', `${this._size}`) + this._deferred = false } // The specified uploadSize might not match the actual amount of data that a source From 3c0245da2ee78f45cea585eb5787e1284decef1e Mon Sep 17 00:00:00 2001 From: Michael Salim Date: Mon, 13 Jan 2025 23:28:59 +0000 Subject: [PATCH 2/5] Feat: Rename _deferred to _uploadLengthDeferred, set value in constructor --- lib/upload.js | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/upload.js b/lib/upload.js index cbd6cf60..97ab24da 100644 --- a/lib/upload.js +++ b/lib/upload.js @@ -108,10 +108,10 @@ class BaseUpload { // An array of upload URLs which are used for uploading the different // parts, if the parallelUploads option is used. - this._parallelUploadUrls = null + this._parallelUploadUrls = null // The remote upload resource's length is deferred - this._deferred = false + this._uploadLengthDeferred = this.options.uploadLengthDeferred } /** @@ -589,7 +589,6 @@ class BaseUpload { if (this.options.uploadLengthDeferred) { req.setHeader('Upload-Defer-Length', '1') - this._deferred = true } else { req.setHeader('Upload-Length', `${this._size}`) } @@ -708,13 +707,13 @@ class BaseUpload { return } - const deferLength = Number.parseInt(res.getHeader("Upload-Defer-Length"), 10) - this._deferred = deferLength === 1 + const deferLength = Number.parseInt(res.getHeader('Upload-Defer-Length'), 10) + this._uploadLengthDeferred = deferLength === 1 const length = Number.parseInt(res.getHeader('Upload-Length'), 10) if ( Number.isNaN(length) && - !this._deferred && + !this._uploadLengthDeferred && !this.options.uploadLengthDeferred && this.options.protocol === PROTOCOL_TUS_V1 ) { From 8107fbdfc785d3e6fa622bbef23b16efe79a8261 Mon Sep 17 00:00:00 2001 From: Michael Salim Date: Mon, 13 Jan 2025 23:29:46 +0000 Subject: [PATCH 3/5] Fix: Missed replace --- lib/upload.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/upload.js b/lib/upload.js index 97ab24da..5f1249cb 100644 --- a/lib/upload.js +++ b/lib/upload.js @@ -828,10 +828,10 @@ class BaseUpload { // If the upload length is deferred, the upload size was not specified during // upload creation. So, if the file reader is done reading, we know the total // upload size and can tell the tus server. - if (this._deferred && (!this.options.uploadLengthDeferred || done)) { + if (this._uploadLengthDeferred && (!this.options.uploadLengthDeferred || done)) { this._size = this._offset + valueSize req.setHeader('Upload-Length', `${this._size}`) - this._deferred = false + this._uploadLengthDeferred = false } // The specified uploadSize might not match the actual amount of data that a source From bc9efaf1f72125e539492500a6d6a42f1fc0fe59 Mon Sep 17 00:00:00 2001 From: Michael Salim Date: Tue, 14 Jan 2025 23:43:05 +0000 Subject: [PATCH 4/5] Fix: Total size not calculated correctly when not last request --- lib/upload.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/upload.js b/lib/upload.js index 5f1249cb..31c5611d 100644 --- a/lib/upload.js +++ b/lib/upload.js @@ -829,7 +829,7 @@ class BaseUpload { // upload creation. So, if the file reader is done reading, we know the total // upload size and can tell the tus server. if (this._uploadLengthDeferred && (!this.options.uploadLengthDeferred || done)) { - this._size = this._offset + valueSize + this._size = done ? this._offset + valueSize : this._source.size req.setHeader('Upload-Length', `${this._size}`) this._uploadLengthDeferred = false } From d7521be25ef92f041db3e06e7447da87f7935e91 Mon Sep 17 00:00:00 2001 From: Michael Salim Date: Tue, 14 Jan 2025 23:43:40 +0000 Subject: [PATCH 5/5] test: Upload-Defer-Length behaviour --- test/spec/test-common.js | 107 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/test/spec/test-common.js b/test/spec/test-common.js index 3d84fbc0..ae1f29a0 100644 --- a/test/spec/test-common.js +++ b/test/spec/test-common.js @@ -1322,5 +1322,112 @@ describe('tus', () => { expect(options.onError).not.toHaveBeenCalled() expect(options.onSuccess).toHaveBeenCalled() }) + + it('should send upload length on the next request when server length is deferred and we know the total size', async () => { + const testStack = new TestHttpStack() + const file = getBlob('hello world') + const options = { + httpStack: testStack, + endpoint: 'http://tus.io/uploads', + uploadUrl: 'http://tus.io/uploads/resuming', + chunkSize: 4, + } + + const upload = new tus.Upload(file, options) + upload.start() + + let req = await testStack.nextRequest() + expect(req.url).toBe('http://tus.io/uploads/resuming') + expect(req.method).toBe('HEAD') + + req.respondWith({ + status: 204, + responseHeaders: { + 'Upload-Defer-Length': 1, + 'Upload-Offset': 5, + }, + }) + + req = await testStack.nextRequest() + expect(req.url).toBe('http://tus.io/uploads/resuming') + expect(req.method).toBe('PATCH') + expect(req.requestHeaders['Upload-Offset']).toBe('5') + expect(req.requestHeaders['Upload-Length']).toBe('11') + expect(req.body.size).toBe(4) + + req.respondWith({ + status: 204, + responseHeaders: { + 'Upload-Offset': 9, + }, + }) + + req = await testStack.nextRequest() + expect(req.url).toBe('http://tus.io/uploads/resuming') + expect(req.method).toBe('PATCH') + expect(req.requestHeaders['Upload-Offset']).toBe('9') + expect(req.requestHeaders['Upload-Length']).toBe(undefined) + + req.respondWith({ + status: 204, + responseHeaders: { + 'Upload-Offset': 11, + }, + }) + }) + + it('should send upload length at the end when server length is deferred and the file is a stream', async () => { + const testStack = new TestHttpStack() + const file = getBlob('hello world') + const options = { + httpStack: testStack, + uploadLengthDeferred: true, + endpoint: 'http://tus.io/uploads', + uploadUrl: 'http://tus.io/uploads/resuming', + chunkSize: 4, + } + + const upload = new tus.Upload(file, options) + upload.start() + + let req = await testStack.nextRequest() + expect(req.url).toBe('http://tus.io/uploads/resuming') + expect(req.method).toBe('HEAD') + + req.respondWith({ + status: 204, + responseHeaders: { + 'Upload-Defer-Length': 1, + 'Upload-Offset': 5, + }, + }) + + req = await testStack.nextRequest() + expect(req.url).toBe('http://tus.io/uploads/resuming') + expect(req.method).toBe('PATCH') + expect(req.requestHeaders['Upload-Offset']).toBe('5') + expect(req.requestHeaders['Upload-Length']).toBe(undefined) + expect(req.body.size).toBe(4) + + req.respondWith({ + status: 204, + responseHeaders: { + 'Upload-Offset': 9, + }, + }) + + req = await testStack.nextRequest() + expect(req.url).toBe('http://tus.io/uploads/resuming') + expect(req.method).toBe('PATCH') + expect(req.requestHeaders['Upload-Offset']).toBe('9') + expect(req.requestHeaders['Upload-Length']).toBe('11') + + req.respondWith({ + status: 204, + responseHeaders: { + 'Upload-Offset': 11, + }, + }) + }) }) })