Skip to content

Commit c22f67f

Browse files
Vija02Marius Kleidl
andauthored
Send upload length when resuming upload with deferred length (#744)
* 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. * Feat: Rename _deferred to _uploadLengthDeferred, set value in constructor * Fix: Missed replace * Fix: Total size not calculated correctly when not last request * test: Upload-Defer-Length behaviour * Send length at end of upload, simplifying logic * Remove redundant test and improve other test --------- Co-authored-by: Marius Kleidl <marius@transloadit.com>
1 parent bef505f commit c22f67f

File tree

3 files changed

+81
-8
lines changed

3 files changed

+81
-8
lines changed

lib/upload.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,10 @@ export class BaseUpload {
105105
// parts, if the parallelUploads option is used.
106106
private _parallelUploadUrls?: string[]
107107

108+
// True if the remote upload resource's length is deferred (either taken from
109+
// upload options or HEAD response)
110+
private _uploadLengthDeferred: boolean
111+
108112
constructor(file: UploadInput, options: UploadOptions) {
109113
// Warn about removed options from previous versions
110114
if ('resume' in options) {
@@ -120,6 +124,8 @@ export class BaseUpload {
120124
// TODO: Remove this cast
121125
this.options.chunkSize = Number(this.options.chunkSize)
122126

127+
this._uploadLengthDeferred = this.options.uploadLengthDeferred
128+
123129
this.file = file
124130
}
125131

@@ -180,7 +186,7 @@ export class BaseUpload {
180186
return
181187
}
182188

183-
if (this.options.uploadLengthDeferred) {
189+
if (this._uploadLengthDeferred) {
184190
this._emitError(
185191
new Error(
186192
'tus: cannot use the `uploadLengthDeferred` option when parallelUploads is enabled',
@@ -239,7 +245,7 @@ export class BaseUpload {
239245
// First, we look at the uploadLengthDeferred option.
240246
// Next, we check if the caller has supplied a manual upload size.
241247
// Finally, we try to use the calculated size from the source object.
242-
if (this.options.uploadLengthDeferred) {
248+
if (this._uploadLengthDeferred) {
243249
this._size = null
244250
} else if (this.options.uploadSize != null) {
245251
this._size = Number(this.options.uploadSize)
@@ -589,7 +595,7 @@ export class BaseUpload {
589595

590596
const req = this._openRequest('POST', this.options.endpoint)
591597

592-
if (this.options.uploadLengthDeferred) {
598+
if (this._uploadLengthDeferred) {
593599
req.setHeader('Upload-Defer-Length', '1')
594600
} else {
595601
if (this._size == null) {
@@ -606,7 +612,7 @@ export class BaseUpload {
606612

607613
let res: HttpResponse
608614
try {
609-
if (this.options.uploadDataDuringCreation && !this.options.uploadLengthDeferred) {
615+
if (this.options.uploadDataDuringCreation && !this._uploadLengthDeferred) {
610616
this._offset = 0
611617
res = await this._addChunkToRequest(req)
612618
} else {
@@ -728,11 +734,14 @@ export class BaseUpload {
728734
throw new DetailedError('tus: invalid Upload-Offset header', undefined, req, res)
729735
}
730736

737+
const deferLength = res.getHeader('Upload-Defer-Length')
738+
this._uploadLengthDeferred = deferLength === '1'
739+
731740
// @ts-expect-error parseInt also handles undefined as we want it to
732741
const length = Number.parseInt(res.getHeader('Upload-Length'), 10)
733742
if (
734743
Number.isNaN(length) &&
735-
!this.options.uploadLengthDeferred &&
744+
!this._uploadLengthDeferred &&
736745
this.options.protocol === PROTOCOL_TUS_V1
737746
) {
738747
throw new DetailedError('tus: invalid or missing length value', undefined, req, res)
@@ -842,7 +851,7 @@ export class BaseUpload {
842851
if (
843852
// @ts-expect-error _size is set here
844853
(end === Number.POSITIVE_INFINITY || end > this._size) &&
845-
!this.options.uploadLengthDeferred
854+
!this._uploadLengthDeferred
846855
) {
847856
// @ts-expect-error _size is set here
848857
end = this._size
@@ -856,9 +865,10 @@ export class BaseUpload {
856865
// If the upload length is deferred, the upload size was not specified during
857866
// upload creation. So, if the file reader is done reading, we know the total
858867
// upload size and can tell the tus server.
859-
if (this.options.uploadLengthDeferred && done) {
868+
if (this._uploadLengthDeferred && done) {
860869
this._size = this._offset + sizeOfValue
861870
req.setHeader('Upload-Length', `${this._size}`)
871+
this._uploadLengthDeferred = false
862872
}
863873

864874
// The specified uploadSize might not match the actual amount of data that a source
@@ -867,7 +877,7 @@ export class BaseUpload {
867877
// in a loop of repeating empty PATCH requests.
868878
// See https://community.transloadit.com/t/how-to-abort-hanging-companion-uploads/16488/13
869879
const newSize = this._offset + sizeOfValue
870-
if (!this.options.uploadLengthDeferred && done && newSize !== this._size) {
880+
if (!this._uploadLengthDeferred && done && newSize !== this._size) {
871881
throw new Error(
872882
`upload was configured with a size of ${this._size} bytes, but the source is done after ${newSize} bytes`,
873883
)

test/spec/test-common.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1330,5 +1330,63 @@ describe('tus', () => {
13301330
expect(options.onError).not.toHaveBeenCalled()
13311331
expect(options.onSuccess).toHaveBeenCalled()
13321332
})
1333+
1334+
it('should send upload length on the last request when length is deferred and we know the total size', async () => {
1335+
const testStack = new TestHttpStack()
1336+
const file = getBlob('hello world')
1337+
const options = {
1338+
httpStack: testStack,
1339+
endpoint: 'http://tus.io/uploads',
1340+
uploadUrl: 'http://tus.io/uploads/resuming',
1341+
chunkSize: 4,
1342+
// No `uploadLengthDeferred: true` here, but the client learns
1343+
// about the deferred length from the HEAD response.
1344+
}
1345+
1346+
const upload = new Upload(file, options)
1347+
upload.start()
1348+
1349+
let req = await testStack.nextRequest()
1350+
expect(req.url).toBe('http://tus.io/uploads/resuming')
1351+
expect(req.method).toBe('HEAD')
1352+
1353+
req.respondWith({
1354+
status: 204,
1355+
responseHeaders: {
1356+
'Upload-Defer-Length': '1',
1357+
'Upload-Offset': '5',
1358+
},
1359+
})
1360+
1361+
req = await testStack.nextRequest()
1362+
expect(req.url).toBe('http://tus.io/uploads/resuming')
1363+
expect(req.method).toBe('PATCH')
1364+
expect(req.requestHeaders['Upload-Offset']).toBe('5')
1365+
expect(req.requestHeaders['Upload-Length']).toBe(undefined)
1366+
expect(req.body.size).toBe(4)
1367+
expect(await req.body.text()).toBe(' wor')
1368+
1369+
req.respondWith({
1370+
status: 204,
1371+
responseHeaders: {
1372+
'Upload-Offset': '9',
1373+
},
1374+
})
1375+
1376+
req = await testStack.nextRequest()
1377+
expect(req.url).toBe('http://tus.io/uploads/resuming')
1378+
expect(req.method).toBe('PATCH')
1379+
expect(req.requestHeaders['Upload-Offset']).toBe('9')
1380+
expect(req.requestHeaders['Upload-Length']).toBe('11')
1381+
expect(req.body.size).toBe(2)
1382+
expect(await req.body.text()).toBe('ld')
1383+
1384+
req.respondWith({
1385+
status: 204,
1386+
responseHeaders: {
1387+
'Upload-Offset': '11',
1388+
},
1389+
})
1390+
})
13331391
})
13341392
})

test/spec/test-web-stream.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ describe('tus', () => {
234234
let req = await testStack.nextRequest()
235235
expect(req.url).toBe('http://tus.io/files/')
236236
expect(req.method).toBe('POST')
237+
expect(req.requestHeaders['Upload-Defer-Length']).toBe('1')
237238

238239
req.respondWith({
239240
status: 201,
@@ -258,6 +259,7 @@ describe('tus', () => {
258259
status: 204,
259260
responseHeaders: {
260261
'Upload-Offset': '0',
262+
'Upload-Defer-Length': '1',
261263
},
262264
})
263265

@@ -306,6 +308,7 @@ describe('tus', () => {
306308
let req = await testStack.nextRequest()
307309
expect(req.url).toBe('http://tus.io/files/')
308310
expect(req.method).toBe('POST')
311+
expect(req.requestHeaders['Upload-Defer-Length']).toBe('1')
309312

310313
req.respondWith({
311314
status: 201,
@@ -341,6 +344,7 @@ describe('tus', () => {
341344
status: 204,
342345
responseHeaders: {
343346
'Upload-Offset': '6',
347+
'Upload-Defer-Length': '1',
344348
},
345349
})
346350

@@ -442,6 +446,7 @@ describe('tus', () => {
442446
status: 200,
443447
responseHeaders: {
444448
'Upload-Offset': '6',
449+
'Upload-Defer-Length': '1',
445450
},
446451
})
447452

0 commit comments

Comments
 (0)