Skip to content

Commit

Permalink
Return 200 on HEAD of completed upload
Browse files Browse the repository at this point in the history
  • Loading branch information
ravi-signal committed Jan 10, 2024
1 parent fa34e2d commit 3ee565a
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 14 deletions.
63 changes: 61 additions & 2 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,15 +342,74 @@ describe('Tus', () => {
expect(await (await getRequest()).text()).toBe('foobar');
});

it('returns 200 for head of completed uploads', async () => {
await createRequest({uploadLength: 4});
await patchRequest(0, 'test');

// https://tus.io/protocols/resumable-upload#head
// The Server MUST always include the Upload-Offset header in the response for a HEAD request, even if the
// offset is 0, or the upload is already considered completed.
const head = await headRequest();
expect(await head.text()).toBe('');
expect(head.status).toBe(200);
expect(head.headers.get('Upload-Offset')).toBe('4');
expect(head.headers.get('Upload-Length')).toBe('4');
});

it('returns upload-length on head if it is known', async () => {
await createRequest({uploadLength: 4});
await patchRequest(0, 'te');

let head = await headRequest();
expect(await head.text()).toBe('');
expect(head.status).toBe(200);
expect(head.headers.get('Upload-Offset')).toBe('2');
expect(head.headers.get('Upload-Length')).toBe('4');

await patchRequest(2, 'st');

head = await headRequest();
expect(await head.text()).toBe('');
expect(head.status).toBe(200);
expect(head.headers.get('Upload-Offset')).toBe('4');
expect(head.headers.get('Upload-Length')).toBe('4');
});

it('handles head with missing upload length', async () => {
await createRequest();
await patchRequest(0, 'te');

let head = await headRequest();
expect(await head.text()).toBe('');
expect(head.status).toBe(200);
expect(head.headers.get('Upload-Offset')).toBe('2');
expect(head.headers.get('Upload-Length')).toBeNull();

await patchRequest(2, 'st', {'Upload-Length': '4'});

head = await headRequest();
expect(await head.text()).toBe('');
expect(head.status).toBe(200);
expect(head.headers.get('Upload-Offset')).toBe('4');
expect(head.headers.get('Upload-Length')).toBe('4');
});

test.each(
[0, 1, PART_SIZE - 1, PART_SIZE, PART_SIZE + 1]
)('rejects incorrect checksum for length=%s', async (bodySize: number) => {
await createRequest({uploadLength: bodySize, checksum: new Uint8Array(32)});
const upload = await patchRequest(0, body(bodySize, {pattern: 'test'}));
expect(upload.status).toBe(415);

// should delete the in-progress upload
expect((await headRequest()).status).toBe(404);
// should delete the in-progress upload: if the object already existed, Upload-Offset should be the object
// length. Otherwise, the head should 404.
const head = await headRequest();
if (head.status === 200) {
expect(head.headers.get('Upload-Offset'))
.toBe(head.headers.get('Upload-Length'));
} else {
expect(head.status).toBe(404);
}
});

test.each(
Expand Down
4 changes: 4 additions & 0 deletions src/retry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export class RetryBucket {
this.params = params;
}

async head(...parameters: Parameters<R2Bucket['head']>): ReturnType<R2Bucket['head']> {
return retry(() => this.bucket.head(...parameters), {params: this.params});
}

async get(...parameters: Parameters<R2Bucket['get']>): ReturnType<R2Bucket['get']> {
return retry(() => this.bucket.get(...parameters), {params: this.params});
}
Expand Down
37 changes: 25 additions & 12 deletions src/uploadHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,20 +193,33 @@ export class UploadHandler {
}

// get the current upload offset to resume an upload
async head(_request: IRequest): Promise<Response> {
const offset: number | undefined = await this.state.storage.get(UPLOAD_OFFSET_KEY);
if (offset == null) {
return error(404);
}
async head(request: IRequest): Promise<Response> {
const r2Key = request.params.id;

return new Response('', {
headers: new Headers({
'Upload-Offset': offset.toString(),
'Upload-Expires': (await this.expirationTime()).toString(),
'Cache-Control': 'no-store',
'Tus-Resumable': TUS_VERSION
})
let offset: number | undefined = await this.state.storage.get(UPLOAD_OFFSET_KEY);
let uploadLength: number | undefined;
if (offset == null) {
const headResponse = await this.retryBucket.head(r2Key);
if (headResponse == null) {
return error(404);
}
offset = headResponse.size;
uploadLength = headResponse.size;
} else {
const info: StoredUploadInfo | undefined = await this.state.storage.get(UPLOAD_INFO_KEY);
uploadLength = info?.uploadLength;
}

const headers = new Headers({
'Upload-Offset': offset.toString(),
'Upload-Expires': (await this.expirationTime()).toString(),
'Cache-Control': 'no-store',
'Tus-Resumable': TUS_VERSION
});
if (uploadLength != null) {
headers.set('Upload-Length', uploadLength.toString());
}
return new Response(null, {headers});
}


Expand Down

0 comments on commit 3ee565a

Please sign in to comment.