From 4dec19e7a8a1e056c9292a376a1a593972e97723 Mon Sep 17 00:00:00 2001 From: oleiade Date: Mon, 29 Jul 2024 17:39:48 +0200 Subject: [PATCH 1/2] Add support for downloading binary files using S3Client.getObject This commit adds an `additionalHeaders` parameter to S3Client.getObject. The operation additionally receives support for the `Accept` header. When the header is provided with the value `application/octet-stream` the operation will, as a result, treat the response as binary content and return an ArrayBuffer. We decided to opt for the header approach, as it doesn't leak nor depend on k6 interfaces, and adopts a standard HTTP behavior, indicating to the server the type of content we expect in return, through the Accept header. --- src/internal/s3.ts | 28 +++++++++++++++++++++++----- tests/internal/s3.js | 15 +++++++++++++++ 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/internal/s3.ts b/src/internal/s3.ts index 8a32448..f11c877 100644 --- a/src/internal/s3.ts +++ b/src/internal/s3.ts @@ -168,7 +168,11 @@ export class S3Client extends AWSClient { * @throws {S3ServiceError} * @throws {InvalidSignatureError} */ - async getObject(bucketName: string, objectKey: string): Promise { + async getObject( + bucketName: string, + objectKey: string, + additionalHeaders: object = {} + ): Promise { // Prepare request const method = 'GET' @@ -177,13 +181,27 @@ export class S3Client extends AWSClient { method: method, endpoint: this.endpoint, path: encodeURI(`/${bucketName}/${objectKey}`), - headers: {}, + headers: { + ...additionalHeaders, + }, }, {} ) + // If the Accept header is set to 'application/octet-stream', we want to + // return the response as binary data. + let responseType: ResponseType = 'text' + if ( + 'Accept' in additionalHeaders && + additionalHeaders['Accept'] !== undefined && + additionalHeaders['Accept'] === 'application/octet-stream' + ) { + responseType = 'binary' + } + const res = await http.asyncRequest(method, signedRequest.url, null, { headers: signedRequest.headers, + responseType: responseType as ResponseType, }) this.handleError(res, 'GetObject') @@ -534,7 +552,7 @@ export class S3Object { etag: string size: number storageClass: StorageClass - data?: string | bytes | null + data?: string | ArrayBuffer | null /** * Create an S3 Object @@ -544,7 +562,7 @@ export class S3Object { * @param {string} etag - S3 object's etag * @param {number} size - S3 object's size * @param {StorageClass} storageClass - S3 object's storage class - * @param {string | bytes | null} data=null - S3 Object's data + * @param {string | ArrayBuffer | null} data=null - S3 Object's data */ constructor( key: string, @@ -552,7 +570,7 @@ export class S3Object { etag: string, size: number, storageClass: StorageClass, - data?: string | bytes | null + data?: string | ArrayBuffer | null ) { this.key = key this.lastModified = lastModified diff --git a/tests/internal/s3.js b/tests/internal/s3.js index 70d2b5b..3c7b26a 100644 --- a/tests/internal/s3.js +++ b/tests/internal/s3.js @@ -73,6 +73,21 @@ export async function s3TestSuite(data) { expect(getNonExistingObjectError).to.be.an.instanceOf(S3ServiceError) }) + await asyncDescribe('s3.getObject [binary]', async (expect) => { + // Act + const gotBinaryObject = await s3Client.getObject( + data.s3.testBucketName, + data.s3.testObjects[0].key, + { Accept: 'application/octet-stream' } + ) + + // Assert + expect(gotBinaryObject).to.be.an('object') + expect(gotBinaryObject.key).to.equal(data.s3.testObjects[0].key) + expect(gotBinaryObject.data).to.be.an('ArrayBuffer') + expect(gotBinaryObject.data.byteLength).to.equal(data.s3.testObjects[0].body.length) + }) + await asyncDescribe('s3.putObject', async (expect) => { // Act let putObectError From 5588da9f43196890bf64f4307153c4d04104e870 Mon Sep 17 00:00:00 2001 From: oleiade Date: Tue, 30 Jul 2024 14:39:29 +0200 Subject: [PATCH 2/2] Fix linting errors --- src/internal/s3.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/internal/s3.ts b/src/internal/s3.ts index f11c877..0ae76c8 100644 --- a/src/internal/s3.ts +++ b/src/internal/s3.ts @@ -1,4 +1,3 @@ -import { bytes } from 'k6' import { parseHTML } from 'k6/html' import http, { RefinedResponse, ResponseType } from 'k6/http'